我有一些代码如下:
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
}
并在每次执行异步调用后增加计数...但这似乎不是一种好方法
有什么建议吗?上述问题是否存在一些设计模式,因为我确定这是常见的情况?
答案 0 :(得分:1)
鉴于您使用的是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,这正是您的目标这样的代码,除非你在后台线程上运行,我强烈建议你这样做。在后台进程完成之前,您始终可以显示“请稍候”消息。
答案 1 :(得分:0)
(注意,我给你两个单独的答案。这个答案尽可能贴近你的问题。)
你的问题是
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<mAllPositions.Count; i++ )
TheSemaphore.WaitOne();
// Now run follow-on code.
您的代码将一直阻止,直到异步操作全部完成。
答案 2 :(得分:0)
你的问题有点不清楚(应该是 count <=
?),但这里有......
您要求的是如何进行同步呼叫。使用异步调用,在进行调用之前,您将分配一个事件处理程序,以便在完成调用时调用,然后进行调用。这意味着您的完成代码与进行调用的代码具有不同的功能。这意味着如果在UI线程上进行异步调用,则在调用时UI不会阻塞。
Silverlight中可以进行同步调用,但您必须确保不在UI线程上执行这些调用。实现此目的的一种方法是启动一个新的后台线程,在后台线程上执行异步调用,但阻止返回直到调用完成。这是一个伪代码示例:
private AutoResetEvent myResetEvent;
private void MyCallFunction(object someParameter) {
if (this.Dispatcher.CheckAccess())
{
Action<object> a = new Action<object>(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中执行此操作时,我从this SO post开始,然后继续this link。但正如Marc gravell在第一篇文章中所说,如果你能避免它,就不要这样做。我需要这样做的唯一一次是当我需要将几个不同的WCF调用的结果聚合到一个返回到UI的结果时(并且这些调用无法组合成一个外观样式的WCF方法)。 p>
答案 3 :(得分:0)
(注意,我给出了两个单独的答案;这采用了与问题完全不同的方法。)
按照Miguel Madero在this URL的代码示例,使用Reactive Framework并行等待所有结果完成,然后继续执行其他任务。
(同一个电子邮件链中还有其他讨论,包括指向this closely related StackOverflow question的链接。)
答案 4 :(得分:0)
按照Tomas博客中描述的Async工作流程(导航到C#段落)的概念,您可以做很多事情。
答案 5 :(得分:0)
//I want to execute code here after each Async call has finished
点的主体中调用相应的autoResetEvent.Wait(); 答案 6 :(得分:0)
另一种选择是增加调用异步进程的foreach循环中的计数器。然后在回调检查中,您将减少计数器并检查相等为零。当计数器返回到零时,所有调用都完成,您的下一个代码就可以运行。请确保以线程安全的方式更改计数器。最简单的方法可能是在递减和测试计数器之前返回UI线程。
答案 7 :(得分:0)
我不认为这是“我想做同步通话”的问题。所有你真正做的就是在它们全部完成时将一系列异步调用绑在一起。
我之前遇到过这种情况,如果你有一个项目列表,需要从服务器异步获取它们的属性(例如某些状态),还想在屏幕上更新一些加载进度指示器。
在这种情况下,您不希望在更新所有项目时阻止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函数。
希望这会有所帮助......