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

我有一个 WPF MVVM应用程序.视图模型具有绑定到视图的几个属性,这些属性由来自数据库的数据直接填充,或者通过驻留在视图模型和数据库之间的wcf服务填充.数据连接模式的选择取决于客户端应用程序的App.config文件中的应用程序设置.我想实现我自己的异步调用服务方法并处理它们的返回值的方法.如果我使用Tasks以下列方式实现它,我想知道是否存在线程问题的可能性:

服务呼叫流程:
viewmodel> ServiceAgent> (MyWCFServiceClient或MyBusinessClient)> MyBusinessClass>数据库
为了使用服务操作,我有一个MyWCFServiceClient类,它实现了IMyWCFService(在添加服务引用时生成).

此外,我有一个MyBusinessClassClient类,它从同一个IMyWCFService接口实现.
因此,MyWCFService和MyBusinessClient都具有相同的方法签名.我选择在生成服务客户端时不生成任何异步方法,因为,如果我这样做,我可能需要在MyBusinessClient中实现由IMyWCFService生成的许多不必要的东西.

假设我有一个方法GetEmployee(int id),它返回一个在IMyWCFService中定义的Employee对象.因此,MyWCFServiceClient和MyBusinessClient类都将具有其实现.

在我的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);                    
            }
        }

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));
        }
    }

这冻结了我的UI.我想避免这种情况.我也想知道这是否是我想要达到的目标的正确方法.我对任务/线程和回调的经验较少,因此我想知道我将来是否会遇到任何问题(线程/内存管理等).

解决方法

@Ananth嘿,我删除评论,因为第二眼我以为我误读了代码.一般来说,当连接到Web服务时,您应该始终将调用视为异步,因为您可以处理过多的延迟,这会冻结任何线程(通常是GUI线程).如果您需要为单个GUI操作进行多个WCF调用,则会更加复杂.这也会变得更糟,因为您的WCF接口被编写为异步调用,但无论如何都会同步并运行.未来混淆的明确原因.

所以我发现最好只处理异步模型,但至少你可以在WCF调用中进行类型检查/转换/返回处理.我做了类似的事情,但我没有使用同步调用,我仍然会使用回调,但我会在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);
    });
}

这使您的典型用法

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

如果你真的想要维护一个同步模型,那么你可以将工作转移到后台线程(但是从GUI线程的角度来看仍然是“异步”).此时,您也可以让GetEmployee方法同步并返回值.这种方式对于使用它的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;
}

然后您的调用代码可能如下所示:

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

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

只是注意,我没有尝试过上面刚刚发布的WCF调用的同步用法(我坚持像我的第一个代码示例那样完整的异步模型),所以我认为它会起作用. (我仍然不建议这样做!)

相关文章

在项目中使用SharpZipLib压缩文件夹的时候,遇到如果目录较深,则压缩包中的文件夹同样比较深的问题。比...
项目需要,几十万张照片需要计算出每个照片的特征值(调用C++编写的DLL)。 业务流程:选择照片...
var array = new byte[4]; var i = Encoding.UTF8.GetBytes(100.ToString(&quot;x2&quot;));//...
其实很简单,因为Combox的Item是一个K/V的object,那么就可以把它的items转换成IEnumerable&lt;Dic...
把.net4.6安装包打包进安装程序。 关键脚本如下: 头部引用字符串对比库 !include &quot;WordFunc....
项目需求(Winform)可以批量打印某个模板,经过百度和摸索,使用iTextSharp+ZXing.NetʿreeSp...