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

c#Task.WhenAll(tasks)和SemaphoreSlim-如何知道所有任务何时完全完成,c#,.net,multithreading,windows-phone-8,thread-safety,C#,.net,Multithreading,Windows Phone 8,Thread Safety,我对C#中的任务/线程管理有一个问题,我想知道是否有一个简单的解决方案 在我的Windows Phone应用程序中,我创建了一系列“上传”任务。每个任务都附加了一个进度/完成处理程序,我在其中检查任务的状态,如果成功完成,我需要执行一些线程安全的文件写入操作 伪代码: 此代码段来自设置我的任务并启动它们运行的方法。我希望此方法仅在所有任务完全完成时返回调用方: var progressCallback = new Progress<UploadOperation>(UploadPro

我对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;
这是我的线程安全方法(由上述事件触发),在该方法中,我使用一个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();
    }
我遇到的困难是,主“任务设置”方法在线程安全方法中的所有上传任务完成轮次之前返回并退出,即,当一些任务在MuploadOperationCompleted方法中仍然没有轮次时,我们点击下面的return语句

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;
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, ...});

 }
}
我的ProcessUploadResult方法如下所示:

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;
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();
            }
        }
}

这似乎终于对我起作用了。我想知道这是一个理想的解决方案吗?

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

你在这里所拥有的是一个延续;任务完成后需要完成的某些工作将基于上一个任务的结果执行其他工作。您应该使用
ContinueWith
方法进行此操作。(或
async
方法,因为它将转换为continuations。)

考虑到你所拥有的一切,这其实很简单:

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

然后,您的
ProcessResult
方法可以像启动
Progress
实例时那样处理这些结果。

谢谢,我想这可能就是我正在寻找的解决方案!出于上下文和异常原因,我建议使用
wait
而不是
ContinueWith
+
Result
。最干净的解决方案可能是创建一个单独的
async
方法,该方法在进行上载及其后处理时一并执行。@user1857360那么您遇到的问题与您在原始问题中提出的问题完全不同。您需要找到一种方法来获取表示事件触发时间的任务,您可以使用
TaskCompletionSource
来实现这一点。您不应该使用
ManualResetEvent
,因为这将同步阻止,而不是异步阻止。@Servy谢谢。在我最初的问题中,我试图解释这个问题,即当我的主调用者返回时,EventHandler尚未完成所有上传线程,因此我需要以某种方式阻止,直到EventHandler完成执行。这就是为什么我尝试使用ManualResetEvent,它实际上似乎对我有效。您能否澄清ManualResetEvent和TaskCompletionSource之间的区别?如果我理解正确,在我的EventHandler中,我需要调用TaskCompletionSource对象上的SetResult(..),以表示处理程序已“完成”?@user1857360 TCS的文档对此有何说明?