Assuming that uiScheduler
is a scheduler that will delegate the calls to the UI thread, I would say that functionally, using the two is indifferent (with the exception that the call to Control.Invoke will block until the call completes, whereas the call to Task
will not, however, you can always use Control.BeginInvoke
to make them semantically equivalent).
From a semantic point of view, I'd say that using Control.Invoke(PaintDelegate)
is a much better approach; when using a Task
you are making an implicit declaration that you want to perform a unit of work, and typically, that unit of work has the context of being scheduled along with other units of work, it's the scheduler that determines how that work is delegated (typically, it's multi-threaded, but in this case, it's marshaled to the UI thread). It should also be said that there is no clear link between the uiScheduler
and the Control
which is linked to the UI thread that the call should be made one (typically, they are all the same, but it's possible to have multiple UI threads, although very rare).
However, in using Control.Invoke
, the intention of what you want to do is clear, you want to marshal the call to the UI thread that the Control
is pumping messages on, and this call indicates that perfectly.
I think the best option, however, is to use a SynchronizationContext
instance; it abstracts out the fact that you need to synchronize calls to that context, as opposed to the other two options, which are either ambiguous about the intent in the call (Task
) or very specific in the way it is being done (Control.Invoke
).