委托的异步调用

Asynchronous calls with delegate

我目前正在使用委托在 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 之前;

也许我在那里遗漏了一些非常简单的东西?....有人可以根据我的情况给我看一个完整的工作样本吗?

你有几个选择。 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 并行 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();