Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UI Freeze when Implementing custom Asynchronous WCF call handling using tasks and callbacks in WPF application

I have a WPF MVVM Application. The View Model has a couple of properties bound to the view, and those properties are populated by data coming from a database directly or through a wcf service that stays in between the view model and the database. The choice of the mode of data connection depends on the app setting in the App.config file of the client application. I want to implement my own way of calling service methods asynchronously and handling their return values. I would like to know if there are chances for threading issues if I implement it the following way using Tasks:

The service call flow: ViewModel > ServiceAgent > (MyWCFServiceClient or MyBusinessClient ) > MyBusinessClass> Database Inorder to consume the service operations I have a MyWCFServiceClient class that implements IMyWCFService (generated when adding the service reference).

Also, I have a MyBusinessClassClient class that implements from the same IMyWCFService interface. Thus, both MyWCFService and MyBusinessClient have the same method signatures. I have opted not to generate any async methods while generating the service client, because, If I do, I may need to implement so many unnecessary stuff generated by IMyWCFService in MyBusinessClient also.

Let’s assume that I have a method GetEmployee(int id) that returns an Employee object, defined in IMyWCFService. Thus both the classes MyWCFServiceClient and MyBusinessClient will have its implementations.

In my ViewModel, I have:

private void btnGetEmployee_Click()
        {
            ServiceAgent sa = new ServiceAgent (); 

            //this call/callback process the service call result

            sa.GetEmployee(1673, (IAsyncResult ar) =>
            {
                Task<Employee> t1 = (Task<Employee>)ar;
                Employee = t1.Result;
                //do some other operation using the result
                //do some UI updation also
            });
        }  


        //this property is bound a label in the view
      private Employee _employee;
        public Employee Employee
        {
            get
            {
                return _ employee;
            }
            set
            {
                _ employee = value;
                OnPropertyChanged(() => Employee);                    
            }
        }

The ServiceAgent class is implemented as the following:

public class ServiceAgent
    {
        private IMyWcfService client;

        public ProxyAgent()
        {
            //The call can go to either MyWCFServiceClient or 
            //MyBusinessClient depending on this setting

            //client = new MyBusinessClient(); 
            //OR

            client = new MyWcfServiceClient();
        }

        public void GetEmployee(int id, AsyncCallback callback)
        {
          //My implementation to execute the service calls asynchronously using tasks
          //I don’t want to use the complex async mechanism generated by wcf service reference ;)

            Task<Employee> t = new Task<Employee>(()=>client.GetEmployee(id));    
            t.Start();

            try
            {
                t.Wait();
            }
            catch (AggregateException ex)
            {
                throw ex.Flatten();
            }

            t.ContinueWith(task=>callback(t));
        }
    }

This is freezing my UI. I want to avoid that. Also I wonder whether this is a proper way for what I want to achieve. I have less experience with tasks/threads and callbacks, and hence I’d like to know whether I will have any issues I the future (threading/memory management etc).

like image 559
Anantha Avatar asked Oct 05 '12 10:10

Anantha


1 Answers

@Ananth heh, I deleted the comment because on second glance I thought I was misreading the code. Generally speaking, when connecting to a web service, you should always treat the call as asynchronous because you can be dealing with excessive lag which would freeze whatever thread (typically the GUI thread). This is compounded if you need to make multiple WCF calls for a single GUI action. This is also worsened because your WCF interface is written like an asynchronous call but then lies and runs synchronously anyway. Definite cause for future confusion.

So I find it's best just to deal with the asynchronous model, but at least you can do the type-checking/casting/return handling within your WCF call. I did something similar, but instead of using synchronous calls, I would still use callbacks, but I'd have the IAsyncResult handled in the WCF call which would then cast it to my expected type and serve it back to the user.

public void GetEmployee(int id, Action<Employee> getEmployeeCompletedHandler)
{
    Task<Employee> t = new Task<Employee>(()=>client.GetEmployee(id));    
    t.Start();
    t.ContinueWith(task=>
    {
        if (getEmployeeCompletedHandler != null)
            getEmployeeCompletedHandler(t1.Result);
    });
}

Which makes your typical usage:

sa.GetEmployee(1673, result => this.Employee = result);

If you really want to maintain an synchronous model, then you can move work to a background thread (but that will still "asynchronous" from the GUI thread's perspective). At this point too, you may as well have your GetEmployee method be synchronous and return the value. This way it's obvious to the API consumer using it that there is no asynchronous operation:

public Employee GetEmployee(int id)
{
    Task<Employee> t = new Task<Employee>(()=>client.GetEmployee(id));    
    t.Start();

    try
    {
        t.Wait();
    }
    catch (AggregateException ex)
    {
        throw ex.Flatten();
    }

    return t.Result;
}

Then your calling code might look like:

//spawn a background thread to prevent the GUI from freezing
BackgroundThread.Spawn(() =>
{
    this.Employee = sa.GetEmployee(1673);
});

Note, BackgroundThread is a custom class that you can make to wrap the creation/spawning of a background thread. I'll leave that implementation detail to you, but I find it's best just to have a managed wrapper for threading because it makes usage so much simpler and abstracts implementation details (using thread pool? new thread? BackgroundWorker? Who cares!)

Just a note, I haven't tried the synchronous usage for WCF calls I just posted above (I stick to a full asynchronous model like my first code sample), so I think it would work. (I still don't recommend doing it this way though!)

like image 162
Chris Sinclair Avatar answered Nov 15 '22 10:11

Chris Sinclair