Refer to a Task without saving the Task object

As an MWE I have a simple Dash.jl app that incremets a progress bar once you hit start and stops it when hitting a stop button.
What I’d like it to do is to start running a task when hitting start and then make some progress animation and stop it once the Task is finished.
However I don’t know how I can refer to the created Task in sequent invokations of the callback, since for storing it I’d have to serialize it somehow, which might be possible, but I don’t know how.
But I thought it would be possible to serialize just the address, that is printed when printing the Task, but I also don’t find a way to access it nor how to retrieve a Task if I had the address.

Is there any way to achieve any of this or is there an alternative way of doing this?

using Dash
using Base.Threads

app = dash()

app.layout = html_div() do
  children = [
    dcc_interval(id = "interval-component", interval = 1000, n_intervals = 0, disabled = true),
    html_progress(id = "progress-bar", value = "0", max = 100),
    html_div(id = "progress-description"),
    html_button("Start", id = "start-button", n_clicks = 0),
    html_button("Stop", id = "stop-button", n_clicks = 0),
    dcc_store(id = "store", data = Dict(:running => false, :complete => false)),  # Initial state
  ]
end

# Single callback to handle both Start and Stop buttons
callback!(
  app,
  Output("interval-component", "disabled"),
  Output("store", "data"),
  Input("start-button", "n_clicks"),
  Input("stop-button", "n_clicks"),
  Input("progress-bar", "value"),
  State("store", "data"),
) do start_clicks, stop_clicks, val, stored_data
  stored_data = Dict(pairs(stored_data))
  if val == "100" && !stored_data[:complete]
    stored_data[:running] = false
    stored_data[:complete] = true
    return true, stored_data
  end
  ctx = callback_context()
  button_id = ""
  !isempty(ctx.triggered) && (button_id = split(ctx.triggered[1].prop_id, ".")[1])
  should_stop = if button_id == "start-button"
    t = Threads.@spawn sleep(10) # i wan't to refer to this task at a later invocation of this callback
    @show t
    stored_data[:complete] = false
    stored_data[:running] = true
    false  # Enable interval
  elseif button_id == "stop-button"
    stored_data[:running] = false
    true  # Disable interval
  else
    stored_data[:running] ? false : true  # No change
  end
  return should_stop, stored_data
end

# Callback to update progress bar and description
callback!(
  app,
  Output("progress-bar", "value"),
  Output("progress-description", "children"),
  Output("interval-component", "n_intervals"),
  Input("interval-component", "n_intervals"),
  State("store", "data"),
) do n_intervals, stored_data
  progress_percentage = min(n_intervals * 10, 100)
  description = "$progress_percentage % Complete"
  if progress_percentage >= 100
    return string(progress_percentage), description, 0  # Reset intervals after completion
  end
  return string(progress_percentage), description, n_intervals
end

run_server(app, debug = true)

What I could come up with is doing the following

using Base.Threads
using Serialization
t = @spawn sleep(100)
open("test.jls", write = true) do io
  serialize(io, convert(UInt, pointer_from_objref(t))
end
t_again = unsafe_pointer_to_objref(Ptr{nothing}(deserialize("test.jls")))

I am just hoping there are no dragons here :see_no_evil:

You can use the Task pointer only so long as you make sure to keep a reference to the Task object on the Julia side somehow (store it in a global Vector or something), otherwise the Task might get GC’d and then a new object might take its pointer, leading to segfaults.