【问题标题】:Silverlight, dealing with Async callsSilverlight,处理异步调用
【发布时间】:2010-02-21 23:08:51
【问题描述】:

我有一些代码如下:

        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
        }

并在每次异步调用后增加计数...但这似乎不是一个好方法

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

【问题讨论】:

    标签: c# .net silverlight


    【解决方案1】:

    鉴于您使用的是 Silverlight,并且您无权访问 Semaphores,您可能正在寻找类似的东西(用记事本编写,因此无法保证完美的语法):

    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,而这正是您要做的处理这样的代码,除非你让它在后台线程上运行,我强烈建议你这样做。在后台进程完成之前,您始终可以显示“请稍候”消息。

    【讨论】:

    【解决方案2】:

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

    你的问题说

    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.
    

    在异步操作全部完成之前,您的代码将阻塞。

    【讨论】:

      【解决方案3】:

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

      按照 Miguel Madero 在this URL 的代码示例,使用反应式框架并行等待所有结果完成,然后继续执行另一个任务。

      (在同一电子邮件链中还有其他讨论,包括指向this closely related StackOverflow question 的链接。)

      【讨论】:

        【解决方案4】:

        您的问题有点不清楚(应该是 count &lt;= 吗?),但是这里是...

        您要求的是如何进行 同步 调用。对于异步调用,在进行调用之前,您会分配一个在调用完成时调用的事件处理程序,然后进行调用。这意味着您的完成代码与进行调用的代码位于不同的函数中。这意味着如果你的异步调用是在 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。但正如马克·格拉维尔在第一篇文章中所说,如果可以避免,就不要这样做。我唯一需要这样做的时候是当我需要将几个不同的 WCF 调用的结果聚合成一个返回给 UI 的结果时(并且这些调用不能组合成一个外观风格的 WCF 方法)。

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:
            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();

            【讨论】:

              【解决方案7】:

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

              【讨论】:

                【解决方案8】:

                我不认为这是一个“我想做一个同步调用”的问题。您真正要做的就是在完成后将一系列异步调用捆绑在一起。

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

                在这种情况下,您不希望在更新所有项目时阻塞 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 函数。

                希望这会有所帮助...

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-03-30
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-03-21
                  相关资源
                  最近更新 更多