将WCF调用转换为异步WCF调用的最佳实践

3

当我试图将所有普通的WCF调用转换为异步WCF调用时,我遇到了一些问题。我发现我需要重构很多代码,但不确定该如何做。我使用了在这里找到的方法,但遇到了需要按顺序进行操作的问题。

private void btnSave_Click(object sender, RoutedEventArgs e)
{

  List<Item> itemList = GetList();
  foreach(Item i in itemList)
  {
    DoSomeWork(i);

    if(i.SomeID == 0)
    {      
       DoSomeMoreWork(i);  
    }

    UpdateRecord(i)  // this can't execute until the above code is complete

  }
}

private void DoSomeWork(Item i)
{
  // call async method
}

private void DoSomeMoreWork(i)
{
  // call async method
}

private void UpdateRecord(item i)
{
  // call async method
}

什么是重构代码以异步方式工作的最佳方法,或者我需要完全重新思考我的逻辑?我真的需要到处插入计数器和开关来确保在其他事情执行之前完成某些事情吗?
编辑:我这样做的原因是在接下来的几个月中,我们将把这个WPF应用程序转换为Silverlight,这需要异步调用。所以我正在尝试将我们的常规WCF调用转换为异步调用以做好准备。我发现这需要一种不同的思考方式。
6个回答

3

针对你所做的事情,我建议处理的真正方法是每个项目只进行一次服务调用,而不是三次。

如果项目列表不是很大,最好使用整个列表进行一次服务调用...

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    foreach(Item i in itemList)  
    {    
        DoAllTheWorkAndUpdate(i);    
    }
}

或者...

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    foreach(Item i in itemList)  
    {    
        if(i.Id == 0)
        {
            DoLotsOfWorkAndUpdate(i);
        }
        else
        {
            DoSomeWorkAndUpdate(i);
        }

    }
}

或者……

private void btnSave_Click(object sender, RoutedEventArgs e)
{  
    List<Item> itemList = GetList();  
    DoTheWorkOnTheWholeList(itemList);
}

换句话说,你可能把一些职责放错了位置 - 我通常更喜欢创建可以通过单个调用的服务。然后,异步性质就不重要了,因为你不需要执行一系列事件。

1
那么,如果您需要在3个不同的服务器上调用3个不同的服务呢?最有可能是2个,但是由于我在扮演魔鬼的代言人,所以我会说3个。例如,我需要在Authorize.NET的CIM Web服务中创建付款配置文件,然后将结果发送到我的自己的服务。 - Simon_Weaver
那么,在这种情况下,您显然必须进行至少n个调用,其中n是单独服务的数量。我仍然会尝试将其保持为每个单独服务的单个调用。此外,如果您正在将数据从一个服务传递到另一个服务,则可能表明责任错位 - 让其中一个服务代表您联系另一个服务可能是更好的解决方案。当然,这并不总是可能的,但要求使用者代码保持两个其他服务同步是需要小心处理的事情 - 存在很多错误的空间。 - kyoryu

2
看一下Juval Lowy(《Programming WCF Services》的作者)的网站,了解在WCF中如何实现异步编程的示例。下载是免费的,你只需要提供你的电子邮件地址。

1

1

我有点困惑,为什么您需要在循环内部使用同步操作时还需要使用异步 WCF 操作。

如果您只是使用异步方法来帮助保持 UI 不挂起,那么您可以使用支持进度更新的 BackgroundWorker 来保持 UI 最新状态,而不使用 Async WCF 调用。

您还应该能够从异步方法的 Completed 事件中调用各种函数。

只需将事件处理程序连接到已完成的事件,然后在启动异步 WCF 调用时将您的 Item 对象作为 userState 参数传递。这样,当每个 Completed 事件触发时,您将拥有它作为参数。这样,您只会在上一个异步调用完成时执行下一步处理。

我不知道这是否真正回答了您的问题。


1
至少有一个使用异步方法的理由是当你真正希望使用同步方法时:当你在Silverlight上工作时,所有都必须是异步的。一般来说,这是有充分理由的,但确实有些麻烦。 - Ken Smith
+1 给 Ken Smith 的评论;我也遇到了同样的问题 https://dev59.com/3EjSa4cB1Zd3GeqPJev_ - Andrei Rînea

0

如果您不使用Silverlight,可以在一个方法中阻塞线程,直到其他方法完成,例如使用ManualResetEvent。但是,在Silverlight中这种方法行不通,因为所有WCF调用都发生在主UI线程上,所以如果您阻塞该线程,一切都会被阻塞。更好的方法是使用回调函数,像这样:

    public delegate void OperationCallback();

    private void btnSave_Click(object sender, RoutedEventArgs e)
    {

        List<Item> itemList = GetList();
        foreach (Item i in itemList)
        {
            DoSomeWork(i, () =>
            {
                if (i.SomeID == 0)
                {
                    DoSomeMoreWork(i, () =>
                    {
                        UpdateRecord(i);
                    });
                }
                else
                {
                    UpdateRecord(i);
                }
            });

        }
    }

    private void DoSomeWork(Item i, OperationCallback callback)
    {
        // call async method then callback when it completes.
        callback();
    }

    private void DoSomeMoreWork(Item i, OperationCallback callback)
    {
        // call async method, then callback when it completes.
        callback();
    }

    private void UpdateRecord(Item i)
    {
        // call async method
    }

这个版本肯定不像同步版本那样清晰明了,但是如果尽可能多地使用 lambda 表达式,仍然可以保持控制流程相当易读。


-1
在Item中添加两个名为SomeWorkDone和SomeMoreWorkDone的布尔属性。创建处理DoSomeWorkCompleted和DoSomeMoreWorkCompleted的方法。在这些方法中,将相应的布尔属性设置为true并调用UpdateRecord。在UpdateRecord中,确保两个Done属性都为true,然后完成调用。
你可能会遇到一些潜在的争用问题,但这应该能让你开始工作。

除非我误解了,否则我认为这不会起作用。您需要阻塞一个方法直到其他方法完成(在这种情况下可以使用某种ResetEvent),或者当另一个方法完成时,您需要以回调的方式调用其中一个方法。 - Ken Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接