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



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

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

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

假设我有一个方法GetEmployee(int id),它返回一个Employees对象,该对象在IMyWCFService中定义。因此,类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调用的同步用法(我坚持使用像我的第一个代码示例一样的完全异步模型),所以我认为它会起作用。(我仍然不建议这样做!)

最新更新