다음을 통해 공유


Wrong Thread Exception Using Task-Based Extension Methods

I’ve been working on a new Windows Store sample that consumes an OData feed of Northwind data (the usual: Customer, Order, Order_Detail entity sets). To best demonstrate how use a new Windows Azure service to send push notifications from an OData service, I needed to be able to support the full set of CRUD behaviors in the app. Since this is a Windows 8 app, I also wanted to take advantage of the full Task-based async support available. I went back to the trusty OData async extension methods written by Phani and to which I added LoadAsync methods (see this previous post).

Once I got everything wired up, I found that read, delete, and update operations worked fine. However, I was getting a threading exception (RPC_E_WRONG_THREAD) on insert (POST) operations when adding a new Order_Detail to an Order, which I did via the .Add(entity) method on the bound DataServiceCollection<Order_Detail> in the view model, as follows:

     public void AddItemToOrder()
    {
        Order_Detail item = new Order_Detail();
        item.OrderID = CurrentOrder.OrderID;
        this.CurrentOrder.Order_Details.Add(item);
    }

Pretty standard OData client stuff, right. Then, even saving the new item to the server works fine, but an exception is raised when the returned entity is updated to the bound entity, then the threading issue occurs.

I was using Phani’s SaveChangesAsync() method, which returns a Task<T> and looks like this:

     public static async Task<DataServiceResponse> SaveChangesAsync(
        this DataServiceContext context, SaveChangesOptions options)
    {
        var queryTask = Task.Factory.FromAsync<DataServiceResponse>(
            context.BeginSaveChanges(options, null, null),
                (queryAsyncResult) =>
                {
                    var results = context.EndSaveChanges(queryAsyncResult);
                    return results;
                });

        return await queryTask;
    }

This looked OK to me, and it worked well for all OData operations, except for create/insert. My original understanding of these APIs was that some of what Task<T> provides to us is marshaling the response back to the correct UI thread—apparently not since I got thread exceptions when the client tried to make updates to bound objects.

I was able to make inserts work by passing the CoreDispatcher from my Window to the view model implementing and using it inside the async Task<T> method (which I didn’t think that I needed to do), as follows:

     public static async Task<DataServiceResponse> 
        SaveChangesAsync(this DataServiceContext context, 
        SaveChangesOptions options, Windows.UI.Core.CoreDispatcher dispatcher)
    {
        var tcs = new TaskCompletionSource<DataServiceResponse>();
        context.BeginSaveChanges(async iar =>
            {

                await dispatcher.RunAsync(
                     Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                    {
                        tcs.SetResult(context.EndSaveChanges(iar));
                    });
            }, null);
        return await tcs.Task;
    }

I’ve attached my latest version of async extensions below.

Well, I did get my sample working well enough (I’ll publish it to Code Gallery in the next few days), but I am still puzzled about the need to use the Dispatcher inside of a Task execution. If anyone has any insights on this behavior or can correct my understanding of this style of async programming, please feel free to share it here in a blog comment.

Cheers,

Glenn Gailey

BTW, the sample that I am working on will demonstrate a new Windows Azure feature: Notification Hubs. Stay tuned!

ODataAsyncExtensions.cs

Comments

  • Anonymous
    March 25, 2014
    Hi, I have the same issue, and have been unable to find any alternative other than what you mention. Have you got any update on why we need to do this? cheers.