Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In a Task, why does IProgress<T> executes correctly in the UI thread but not an Action<T>?

On the following piece of code :

Task UpdateMedias<TProperty>(Expression<Func<Media, TProperty>> property, Func<Media, TProperty> func)
{
    var medias = GetSelectedMedias().ToList();
    IProgress<double> progress = new Progress<double>(d => barQueueProgress.EditValue = d);
    Action<Media, Expression<Func<Media, TProperty>>, Func<Media, TProperty>> action =
        (media, expression, arg3) => UpdateMedia(media, expression, arg3);
    Task task = Task.Run(() =>
    {
        var i = 0;
        foreach (var media in medias)
        {
            progress.Report(1.0d / medias.Count * ++i);
            action(media, property, func);
        }
    });
    Task with = task.ContinueWith(s =>
    {
        progress.Report(0.0d);
        GridControl1.RefreshData();
    });
    return with;
}

If I don't encompass action with Dispatcher.BeginInvoke it will complain with The calling thread cannot access this object because a different thread owns it. while for progress there is no need to do so.

How come IProgress<T> works without the need of Dispatcher.BeginInvoke ?

like image 727
aybe Avatar asked Jan 12 '23 04:01

aybe


1 Answers

Because internally Progress stores a reference to what was in SynchronizationContext.Current when it was constructed, and it fires the event to that context when reporting progress.

It was specifically designed for updating the UI from a non-UI thread. If it didn't do this, there wouldn't be nearly as much reason to use it, nor is it hard at all to do.

Here is what I use as an implementation of Progress pre .NET 4.5. It won't be identical to the .NET implementation, but it'll give you a pretty good idea of what's going on:

public interface IProgress<T>
{
    void Report(T data);
}

public class Progress<T> : IProgress<T>
{
    SynchronizationContext context;
    public Progress()
    {
        context = SynchronizationContext.Current
            ?? new SynchronizationContext();
    }

    public Progress(Action<T> action)
        : this()
    {
        ProgressReported += action;
    }

    public event Action<T> ProgressReported;

    void IProgress<T>.Report(T data)
    {
        var action = ProgressReported;
        if (action != null)
        {
            context.Post(arg => action((T)arg), data);
        }
    }
}
like image 173
Servy Avatar answered Jan 29 '23 18:01

Servy