Silverlight,处理异步调用

我有一些代码如下:

foreach (var position in mAllPositions) { DoAsyncCall(position); } //I want to execute code here after each Async call has finished 

那我怎么能做到这一点呢?
我可以这样做:

  while (count < mAllPositions.Count) { //Run my code here } 

并且在每次执行异步调用之后增加计数…但这似乎不是一种好方法

任何建议? 是否有一些上述问题的设计模式,因为我确定这是一个常见的场景?

鉴于您正在使用Silverlight,并且您无法访问信号量,您可能正在寻找类似的东西(用记事本编写,因此没有关于完美语法的承诺):

 int completedCallCount = 0; int targetCount = mAllPositions.Count; using (ManualResetEvent manualResetEvent = new ManualResetEvent(false)) { proxy.DoAsyncCallCompleted += (s, e) => { if (Interlocked.Increment(ref completedCallCount) == targetCount) { manualResetEvent.Set(); } }; foreach (var position in mAllPositions) { proxy.DoAsyncCall(position); } // This will wait until all the events have completed. manualResetEvent.WaitOne(); } 

但是 ,Silverlight强制您异步加载数据的一个好处是,您在进行服务调用时必须努力锁定UI,这正是您要对此类代码执行的操作,除非您有它运行在后台线程上,我强烈建议你这样做。 在后台进程完成之前,您始终可以显示“请稍候”消息。

(注意,我给你两个单独的答案。这个答案尽可能贴近你的问题。)

你的问题说

 while{ count >= mAllPositions.Count ) { //Run my code here } 

但我猜你真正的意思是:

 while( count < mAllPositions.Count ) ; // do nothing -- busy wait until the count has been incremented enough // Now run my code here 

??

如果是这样,那么你可以通过使用信号量更有效地完成同样的事情:

 Semaphore TheSemaphore = new Semaphore( 0, mAllPositions.Count ); 

每次异步调用完成后,释放信号量。

 TheSemaphore.Release(); 

在执行最终代码之前,请确保已释放信号量所需的次数:

  for( int i=0; i 

您的代码将阻塞,直到异步操作全部完成。

(注意,我给出了两个单独的答案;这采用了与问题完全不同的方法。)

按照Miguel Madero在此URL上的代码示例,使用Reactive Framework并行等待所有结果完成,然后继续执行其他任务。

(在同一个电子邮件链中还有其他讨论,包括这个密切相关的StackOverflow问题的链接。)

你的问题有点不清楚(应该count <= ?),但是这里......

您要求的是如何进行同步通话。 使用异步调用,在进行调用之前,您将分配一个事件处理程序,以便在完成调用时调用,然后进行调用。 这意味着您的完成代码与进行调用的代码具有不同的function。 这意味着如果在UI线程上进行异步调用,则在调用时UI不会阻塞。

Silverlight中可以进行同步调用,但您必须确保不在UI线程上执行这些调用。 实现此目的的一种方法是启动一个新的后台线程,在后台线程上执行异步调用,但阻止返回直到调用完成。 这是一个伪代码示例:

 private AutoResetEvent myResetEvent; private void MyCallFunction(object someParameter) { if (this.Dispatcher.CheckAccess()) { Action a = new Action(MyCallFunction); a.BeginInvoke(someParameter, null, null); return; } myResetEvent = new AutoresetEvent(); myAsyncCall.CallCompleted += new EventHandler<>(myAsyncCall_CallCompleted); myAsyncCall.DoAsyncCall(someParameter); myResetEvent.WaitOne(); //increment your count here } private void myAsyncCall_CallCompleted(object sender, SomeEventArgs e) { if (e.Error == null && !e.Cancelled) { if (myResetEvent != null) myResetEvent.Set(); } } 

请注意,此代码不是特别线程安全或生产就绪 - 它只是一个快速示例。

这里发生的是当你输入MyCallFunction ,它会检查它是否在UI线程上运行,如果是,那么它会在后台线程上重新调用它自己。 然后它设置一个AutoResetEvent,并进行异步调用。 然后它暂停在myResetEvent对象上,直到它从调用完成处理程序设置(或“发出信号”),此时代码继续执行。 请注意,在不首先确保返回UI线程的情况下,不应尝试直接从此类代码访问控件。

当我开始研究如何在Silverlight中执行此操作时,我开始使用此SOpost ,并继续使用此链接 。 但正如Marc gravell在第一篇文章中所说,如果你能避免它,就不要这样做。 我需要这样做的唯一一次是当我需要将几个不同的WCF调用的结果聚合到一个返回到UI的结果时(并且这些调用无法组合成一个外观样式的WCF方法)。

按照Tomas博客描述的Async工作流概念(导航到C#段落),您可以做很多事情。

http://tomasp.net/blog/csharp-async.aspx

  1. 设置静态autoResetEvent
  2. 设置ThreadPoolQueueUserWorkItems
  3. 循环中的ForEach项,对项进行排队,并将autoResetEvent作为回调参数传入。
  4. 在回调中,执行工作然后调用autoresetevent.Set();
  5. 在主体中//I want to execute code here after each Async call has finished调用相应的autoResetEvent.Wait();

另一种选择是增加调用异步进程的foreach循环中的计数器。 然后在回调检查中,您将减少计数器并检查相等为零。 当计数器返回到零时,所有调用都完成,您的下一个代码可以运行。 请确保以线程安全的方式更改计数器。 最简单的方法可能是在递减和测试计数器之前返回UI线程。

我不认为这是“我想做同步通话”的问题。 所有你真正做的就是在它们全部完成时将一系列异步调用绑在一起。

我之前遇到过这种情况,你有一个项目列表,需要从服务器异步获取它们的属性(例如一些状态),但也想在屏幕上更新一些加载进度指示器。

在这种情况下,您不希望在更新所有项目时阻止UI线程(如果有替代方法,阻止线程仍然是邪恶的)。

所以我使用了这样的控件类:

 public class GroupAction { public delegate void ActionCompletion(); private uint _count = 0; private ActionCompletion _callback; private object _lockObject = new object(); public GroupAction(uint count, ActionCompletion callback) { _count = count; _callback = callback; } public void SingleActionComplete() { lock(_lockObject) { if (_count > 0) { _count--; if (_count == 0) _callback(); } } } } 

您可以使用计数(对于项目数)和在完成所有项目时执行的回调来创建此操作。 基本上就像一个信号量更多的控制,没有线程担心。

调用代码看起来像这样(我基于调用w3schools.com上可以找到的标准临时转换Web服务的示例)。

  private void DoGroupCall() { uint count = 5; GroupAction action = new GroupAction(count, () => { MessageBox.Show("Finished All Tasks"); }); for (uint i = 0; i < count; i++) { TempConvertHttpPost proxy = new TempConvertHttpPostClient(new BasicHttpBinding(), new EndpointAddress("http://localhost/webservices/tempconvert.asmx")); CelsiusToFahrenheitRequest request = new CelsiusToFahrenheitRequest() { Celsius = "100" }; proxy.BeginCelsiusToFahrenheit(request, (ar) => Deployment.Current.Dispatcher.BeginInvoke( () => { CelsiusToFahrenheitResponse response = proxy.EndCelsiusToFahrenheit(ar); // Other code presumably... action.SingleActionComplete(); } ), null); } } 

注意如何使用内联回调委托定义GroupAction实例,然后在每次返回服务时调用SingleCallBack函数。

希望这可以帮助…