3

I'm working on an app and trying to do everything nice and best practice; here's the code that doesn't do what I want it to:

async public Task DoStuff()
{
    DispatcherAdapter.Current.BeginInvoke(() => { myObject.SomeProperty = "new value"; });
    await DatabaseAdapter.Current.SaveItem(TableName, myObject);
}

The code above doesn't save the item to the database with the updated property value, and I imagine this is because BeginInvoke() doesn't necessarily finish before my awaited async method does its work. DispatcherAdapter just uses Deployment.Current.Dispatcher in the background, like this:

    public override void BeginInvoke(Action a)
    {
        Deployment.Current.Dispatcher.BeginInvoke(a);
    }

Here's what I've looked into, and doesn't work:

  • awaiting the BeginInvoke() call - wont work because it doesn't return Task, and it still drills down to not being able to tell when Dispatcher.BeginInvoke actually finishes
  • trying to work with the DispatcherOperation object returned from BeginInvoke() - the Completed event that's available in normal .NET is not available for Windows Phone 8
  • using SynchronizationContext.Send() - DispatcherSynchronizationContext.Current is always null, so doesn't work...see irrelevant code below:

    public async override Task Invoke(Action a)
    {
        await Task.Factory.StartNew(() => { DispatcherSynchronizationContext.Current.Send(new SendOrPostCallback(delegate { a.Invoke(); }), null); });
    }
    

Basically, how can I avoid invalid-cross-thread access AND ensure code runs in a specific order within an asynchronous method in Windows Phone 8? I guess I could move the .SaveItem to go within the BeginInvoke...but it feels like a cheat way out, I still can't await that BeginInvoke call (which makes putting it in an async method pretty pointless) and it would be nice to know what I should be doing / other people have done in this situation.

Community
  • 1
  • 1
Henry C
  • 4,781
  • 4
  • 43
  • 83

2 Answers2

2

The easiest way to do this is to call your async method from the UI thread. Then you can do this:

public async Task DoStuffAsync()
{
  myObject.SomeProperty = "new value";
  await DatabaseAdapter.Current.SaveItem(TableName, myObject);
}

If your async method needs to do some operation on a background thread, wrap it in a Task.Run.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I'm not sure if its feasible to ensure it's called from the UI thread, as I'm aiming to keep the code within the Portable Class Library stuff (ie in the View Models), instead of the Views directly – Henry C Dec 05 '12 at 13:10
  • ViewModel objects are logically considered part of the UI context, largely due to data binding. That's the approach I take for all my `async` MVVM projects - it's more testable and portable than taking a dependency on a `Dispatcher`-related type. – Stephen Cleary Dec 05 '12 at 13:31
1

I ended up creating my own DispatcherSynchronizationContext, and everything seemed to work:

    public async override Task Invoke(Action a)
    {
        DispatcherSynchronizationContext dsc = new DispatcherSynchronizationContext(Deployment.Current.Dispatcher);
        await Task.Factory.StartNew(() => { dsc.Send(new SendOrPostCallback(delegate { a.Invoke(); }), null); });
    }

I'm still not sure if this is the recommended way of doing this, or if there are any gotchas in using DispatcherSynchronizationContext in this way...but I can await this helper method that affects the UI thread, so that's a start...

Henry C
  • 4,781
  • 4
  • 43
  • 83