c#Task.WhenAll(tasks)和SemaphoreSlim - 如何知道所有任务何时完全完成

时间:2014-11-03 16:19:40

标签: c# .net multithreading windows-phone-8 thread-safety

我遇到了C#中的任务/线程管理问题,我想知道是否有一个解决我问题的简单方法。

在我的Windows Phone应用程序中,我创建了一堆“上传”任务。每个任务都附有一个进度/完成处理程序,我在其中检查任务的状态,如果成功完成,我需要执行一些线程安全的文件写操作。

的伪代码:

此代码段来自设置我的任务并开始运行的方法。我希望这个方法只在所有任务完全完成后返回调用者:

var progressCallback = new Progress<UploadOperation>(UploadProgress);
for (var i = 0; i < uploads.Count; i++)
{
    uploadTasks[i] = uploads[i].StartAsync().AsTask(ct, progressCallback);
}
await Task.WhenAll(uploadTasks);
// all uploads complete!
return;

我的进度/任务完成处理程序检查状态,如果确定,我引发一个事件,触发对线程安全方法的调用,该方法需要执行一些文件写入:

private void UploadProgress(UploadOperation upload){
  ....
  if(upload == successful)
  {
      //raise an event which results in a call to a thread-safe method
      //to perform post-upload tasks          
  }
  ....
}

这是我的线程安全方法(由上面的事件触发),其中我使用SemaphoreSlim对象来确保一次只有一个线程可以访问它:

private static readonly SemaphoreSlim _SemaphoreSlim = new SemaphoreSlim(1);
private async void OnItemUploadOperationCompleted(...)
{
    await _SemaphoreSlim.WaitAsync();
    try
    {
       //perform some await-able file updates / writes
    }
    catch(..){}

    finally
    {
        //release lock
        _SemaphoreSlim.Release();
    }

我遇到的困难是主要的“任务设置”方法在所有上传任务在线程安全方法中完成之前返回并退出,即我们点击下面的返回语句,而几个任务仍然尚未在OnItemUploadOperationCompleted方法中使用。

await Task.WhenAll(uploadTasks);
// all uploads complete!
return;

如果有更好的方法,我正在努力解决这个问题。有没有办法确定所有任务是否已“完全”完成,它们是否仍然挂起并等待队列进入线程安全操作?基本上我需要知道所有处理何时完成,包括每个任务的所有线程安全后处理。

似乎“Task.WhenAll”返回得太早,可能是在初始任务本身完成时,而不是在子任务/衍生任务完成时?

编辑:

我遵循了Servy的建议(下面的第一个答案),如下所示:

foreach (var upload in uploads)
{
    uploadTasks.Add(upload.StartAsync().AsTask(ct, progressCallback).ContinueWith(task => ProcessUploadResult(task.Result), ct));
}

await Task.WhenAll(uploadTasks);

// all uploads complete?
return;

我的ProcessUploadResult方法如下所示:

private void ProcessUploadResult(UploadOperation uploadResult){

 ....
 //pseudo code
 if(uploadResult == success){

     //RAISE an Event!
     //The listener of this event processes the result - 
     // - performs some file writes / updates
     // - therefore the handler for this eventy MUST be thread safe.
     OnItemUploadOperationCompleted(this, ...});

 }
}

所以,我的困难是,即使使用这种方法,事件处理程序仍然没有在“Task.WhenAll(...)”返回时完成处理所有上传。仍有线程在等待访问该事件处理程序。

所以,我想我已经找到了一个解决方案,我想知道它是否是一个很好的解决方案,使用ManualResetEvent:

 ....
 //pseudo code
 if(uploadResult == success){

     //RAISE an Event!
     //The listener of this event processes the result - 
     // - performs some file writes / updates
     // - therefore the handler for this eventy MUST be thread safe.


     var wait = new ManualResetEvent(false);

     // pass the reference to ManualResetEvent in the event Args
     OnItemUploadOperationCompleted(this, new MyEventArgs {waiter = wait});

     wait.WaitOne();

 }
}

现在在我的处理程序中,我使用该ManualResetEvent对象向等待线程发出信号,告知我们已经完成处理上传响应:

private async void OnItemUploadOperationCompleted(object sender, UploadResultEventArgs e)
{
        await _SemaphoreSlim.WaitAsync();
        try
        {
             //perform async file writes
        }
        catch{
               ....
        }
        finally
        {
            //release lock
            _SemaphoreSlim.Release();

            //signal we are done here
            var waiter = e.Waiter as ManualResetEvent;
            if (waiter != null)
            {
                waiter.Set();
            }
        }
}

这最终似乎对我有用。我想知道它是否是理想的解决方案?

1 个答案:

答案 0 :(得分:1)

Progress类用于根据操作的当前进度更新UI,并且该操作不应关心这些更新是什么或何时完成。

你在这里有一个延续;在任务完成后,需要完成一些工作,以根据上一个任务的结果执行其他工作。您应该使用ContinueWith方法。 (或async方法,因为它将被转换为延续。)

鉴于你拥有的所有东西,这实际上非常简单:

uploadTasks[i] = uploads[i].StartAsync()
    .AsTask(ct, progressCallback)
    .ContinueWith(task => ProcessResult(task.Result));

您的ProcessResult方法可以像处理Progress实例时一样处理这些结果。