7

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

服务调用流程:ViewModel > ServiceAgent > (MyWCFServiceClient or MyBusinessClient) > MyBusinessClass> Database 为了使用服务操作,我有一个 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));
        }
    }

这冻结了我的用户界面。我想避免这种情况。另外我想知道这是否是我想要实现的正确方法。我对任务/线程和回调的经验较少,因此我想知道我将来是否会遇到任何问题(线程/内存管理等)。

4

1 回答 1

3

@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 调用的同步使用(我坚持使用完整的异步模型,就像我的第一个代码示例一样),所以我认为它会起作用。(我仍然不建议这样做!)

于 2012-10-05T10:53:51.920 回答