10

In .NET, Windows 8 and Windows Phone 7 I have code similar to this:

public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
{
    if (dispatcher.CheckAccess())
    {
        action();
    }
    else
    {
        dispatcher.Invoke(action);
    }
}

How would I do something in the portable class library? It would be nice to have one platform agnostic implementation of this. My idea is to use the TPL which is not available in WP7 but definitely will be soon.

// PortableDispatcher must be created on the UI thread and then made accessible 
// maybe as a property in my ViewModel base class.
public class PortableDispatcher
{
    private TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    public void Invoke(Action action)
    {
        if (Alread on UI thread. How would I do this.)
        {
            action();
        }

        Task.Factory.StartNew(
            action, 
            CancellationToken.None,
            TaskCreationOptions.None,
            taskScheduler);
    }
}

The only thing I'm unsure about is what the performance implications of this would be. Perhaps I'll do some tests.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311

2 Answers2

14

You can use SynchronizationContext.Post or Send method. It is portable, and when you use a UI framework with a dispatcher then the current synchronization context will delegate the work to the Dispatcher.

Specifically, you can use the following code:

void InvokeIfRequired(this SynchroniationContext context, Action action)
{
    if (SynchroniationContext.Current == context)
    {
        action();
    }
    else
    {
        context.Send(action) // send = synchronously
        // context.Post(action)  - post is asynchronous.
    }  
}
svick
  • 236,525
  • 50
  • 385
  • 514
Alex Shtoff
  • 2,520
  • 1
  • 25
  • 53
  • Nice one. How would you create the extension method I use above for the dispatcher. Specifically how do you check if you're already running on the UI thread instead of doing dispatcher.CheckAccess(). – Muhammad Rehan Saeed Jun 29 '12 at 08:35
  • Also is Post the Equivelant of BeginInvoke and Send the equivelant of Invoke? – Muhammad Rehan Saeed Jun 29 '12 at 08:38
  • 1
    You don't need to check before posting. Post already does the proper checks. – Panagiotis Kanavos Jun 29 '12 at 09:45
  • Doesn't look like it. Reflector shows this: `public virtual void Post(SendOrPostCallback d, object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state); }` – Muhammad Rehan Saeed Jun 29 '12 at 11:48
  • 4
    Note that SynchronizationContext.Send is not supported in Metro style apps- you will get a NotSupportedException. The method is now marked obsolete in portable class libraries because of this. See http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.send(v=vs.110).aspx – Daniel Plaisted Jul 02 '12 at 16:20
  • @DanielPlaisted Does this mean that is not possible to handle dispatcher in a portable class (if supporting Metro style apps)? Interestingly, it says "Obsolete (compiler warning) in Portable Class Library" for PCL, but "Supported in: Windows 8" for .NET for Windows Store apps. Is POST a good alternative (async). – tofutim Nov 21 '12 at 15:44
  • 1
    context.Post asks for a SendOrPostCallback – tofutim Nov 21 '12 at 15:55
2

With the advent of the TPL. I came up with a slightly improved version of the accepted answer which returns a task and can be await'ed using the new async and await keywords.

public Task RunAsync(Action action)
{
    TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>();

    if (this.synchronizationContext == SynchronizationContext.Current)
    {
        try
        {
            action();
            taskCompletionSource.SetResult(null);
        }
        catch (Exception exception)
        {
            taskCompletionSource.SetException(exception);
        }
    }
    else
    {
        // Run the action asyncronously. The Send method can be used to run syncronously.
        this.synchronizationContext.Post(
            (obj) => 
            {
                try
                {
                    action();
                    taskCompletionSource.SetResult(null);
                }
                catch (Exception exception)
                {
                    taskCompletionSource.SetException(exception);
                }
            }, 
            null);
    }

    return taskCompletionSource.Task;
}
Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • 2
    Nice. Looks like you created a class but only posted the method so a (maybe obvious) note for others. this.synchronizationContext has to be assigned in the thread you want the action to run in before calling the method. I actually modified above a bit and created an extension: public Task RunAsync(this SynchronizationContext context, Action action). Now its more like original answer as it can stand alone. The best of both answers. :) – Wes Apr 07 '14 at 21:26