使用委托进行异步调用

时间:2015-01-16 17:58:46

标签: c# delegates asynchronous

我目前正在使用委托在for循环中进行异步调用,我遇到的问题是 - 我怎么知道这些异步调用何时完成?

例如:

         public delegate string GetMergeSectionCaller(string something1, out int threadId);

         public Dataset GetDataset (param1, param2) {
               int threadId;
               Dataset ds = new Dataset;

               using (myConnection) {
                   myConnection.StartConnection();
                   GetMergeSectionCaller caller = new GetMergeSectionCaller(GetMergeSection);
                   foreach (var r in anObjectList) {
                       IAsyncResult result = caller.BeginInvoke(r.ToString(), out threadId, null, null);
                   }

                   //I want to do here is wait for the above every single foreach BeginInvoke to finish; then do the below job
                   ds = GetFinalData();
               }
               //do more thing to ds here;
               return ds;
            }

         public void GetMergeSectionCaller(string something1, out int threadId) {
             //doing superlong long job
             //in my actual case, it's actually inserting data to db etc

             Thread.Sleep(5000);
             threadId = Thread.CurrentThread.ManagedThreadId;
         }

所以我尝试了不同的方法;比如将回调传递给我的BeginInvoke和EndInvoke,但是仍然 - 我错过了一种方法来阻止剩下的代码在我完成foreach之前运行;

也许我错过了一些非常简单的东西?....有人可以根据我的情况向我展示一份完整的工作样本吗?

1 个答案:

答案 0 :(得分:2)

您有几个选择。 IAsyncResult具有WaitHandle属性,您可以使用该属性等待。

var results = new List<WaitHandle>();
foreach (var r in anObjectList) {
   results.Add(caller.BeginInvoke(r.ToString(), out threadId, null, null).WaitHandle);
}
// wait for all results to complete
WaitHandle.WaitAll(results.ToArray());

另一种选择是创建一个ManualResetEvent和一个计数器,并在计数器达到0时从回调中重置事件。这种方法的优点是你只能创建一个可等待的对象,但你也必须管理柜台。

最后,另一种选择是使用新的Task-based API,它为等待任务提供了更好的编程抽象。

要指出的其他一些事情:

  • 不要使用Thread.Sleep - 可以用它来测试你的代码,但是一旦你确认你的异步代码有效,就不要使用它!
  • 不要依赖委托BeginInvoke - 这不是真正的并行性。它只是推迟了方法的调用,但它没有按照你的想法做到。相反,如果要并行执行这些方法,请使用Task,ThreadPool或Thread。

更新

您也可以使用TPL parallel for循环,这可能更接近您原来希望实现的目标:

Parallel.ForEach(anObjectList, anObjectItem => {
    // do something with anObjectItem
});
// this parallelizes the for-loop iterations

更新2

以下是如何使用ThreadPool和ManualResetEvent中的工作线程运行任务。

ManualResetEvent mreComplete = new ManualResetEvent(false);
int callsRemaining;

GetMergeSectionCaller caller = new GetMergeSectionCaller(GetMergeSection);
callsRemaining = anObjectList.Count;
mreComplete.Reset();
foreach (var r in anObjectList) {
    ThreadPool.QueueUserWorkItem((Action)delegate {
        caller(r.ToString());
        lock{
           if(--callsRemaining==0) mreComplete.Set();
        }
    }
}
mreComplete.Wait();