简体   繁体   English

在WPF应用程序中使用任务和回调实现自定义异步WCF调用处理时,UI冻结

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

I have a WPF MVVM Application. 我有一个WPF MVVM应用程序。 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. 视图模型具有绑定到视图的几个属性,这些属性由来自数据库的数据直接填充,或者通过驻留在视图模型和数据库之间的wcf服务填充。 The choice of the mode of data connection depends on the app setting in the App.config file of the client application. 数据连接模式的选择取决于客户端应用程序的App.config文件中的应用程序设置。 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: 如果我使用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). 服务调用流:ViewModel> ServiceAgent>(MyWCFServiceClient或MyBusinessClient)> MyBusinessClass> Database Inorder使用服务操作我有一个实现IMyWCFService的MyWCFServiceClient类(在添加服务引用时生成)。

Also, I have a MyBusinessClassClient class that implements from the same IMyWCFService interface. 此外,我有一个MyBusinessClassClient类,它从同一个IMyWCFService接口实现。 Thus, both MyWCFService and MyBusinessClient have the same method signatures. 因此,MyWCFService和MyBusinessClient都具有相同的方法签名。 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. 我选择在生成服务客户端时不生成任何异步方法,因为,如果我这样做,我可能需要在MyBusinessClient中实现由IMyWCFService生成的许多不必要的东西。

Let's assume that I have a method GetEmployee(int id) that returns an Employee object, defined in IMyWCFService. 假设我有一个方法GetEmployee(int id),它返回一个在IMyWCFService中定义的Employee对象。 Thus both the classes MyWCFServiceClient and MyBusinessClient will have its implementations. 因此,MyWCFServiceClient和MyBusinessClient类都将具有其实现。

In my ViewModel, I have: 在我的ViewModel中,我有:

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: ServiceAgent类实现如下:

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. 这冻结了我的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). 我对任务/线程和回调的经验较少,因此我想知道我将来是否会遇到任何问题(线程/内存管理等)。

@Ananth heh, I deleted the comment because on second glance I thought I was misreading the code. @Ananth嘿,我删除了评论,因为第二眼我以为我误读了代码。 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). 一般来说,当连接到Web服务时,您应该始终将调用视为异步,因为您可以处理过多的延迟,这会冻结任何线程(通常是GUI线程)。 This is compounded if you need to make multiple WCF calls for a single GUI action. 如果您需要为单个GUI操作进行多个WCF调用,则会更加复杂。 This is also worsened because your WCF interface is written like an asynchronous call but then lies and runs synchronously anyway. 这也会变得更糟,因为您的WCF接口被编写为异步调用,但无论如何都会同步并运行。 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. 所以我发现最好只处理异步模型,但至少你可以在WCF调用中进行类型检查/转换/返回处理。 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. 我做了类似的事情,但我没有使用同步调用,我仍然会使用回调,但我会 WCF调用中处理IAsyncResult,然后将其转换为我期望的类型并将其提供给用户。

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). 如果你真的想维护一个同步模型,那么你可以将工作转移到后台线程(但是从GUI线程的角度来看仍然是“异步”)。 At this point too, you may as well have your GetEmployee method be synchronous and return the value. 此时,您也可以使GetEmployee方法同步并返回值。 This way it's obvious to the API consumer using it that there is no asynchronous operation: 这种方式对于使用它的API使用者来说显而易见的是没有异步操作:

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. 注意, BackgroundThread是一个自定义类,您可以用它来包装后台线程的创建/生成。 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!) 我将把这个实现细节留给你,但我发现最好只有一个托管的线程包装器,因为它使用得更简单并抽象实现细节(使用线程池?新线程?BackgroundWorker?谁在乎!)

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. 请注意,我没有尝试过上面刚刚发布的WCF调用的同步用法(我坚持像我的第一个代码示例那样完全异步模型),所以我认为它会起作用。 (I still don't recommend doing it this way though!) (我仍然不建议这样做!)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM