ContinueWith任务完成前任务运行

问题描述:

背景信息:我正在尝试创建一个能够同时处理5个并发操作的单例类。每个操作由SomeAsyncMethod表示。ContinueWith任务完成前任务运行


该方法存在于单例类中。

consumersConcurrentDictionary<int,Task>

我的问题:由于某种原因,ContinueWith委托SomeAsyncMethod完成之前运行。我知道这种情况发生的原因是因为我有另一种方法,手表instance.Consumers.Count - 在SomeAsyncMethod完成之前计数为0

这是为什么?

public bool TryAddDequeueRequest() 
    { 
     if (instance.Consumers.Count < 5) 
     { 
      Task bogusTask; 
      Task newTask = new Task(SomeAsyncMethod); 

      //RUNS AFTER THE REQUEST IS COMPLETED 
      newTask.ContinueWith(t => 
      { 
       instance.Consumers.TryRemove(t.Id, out bogusTask); 
      }); 

      //WE ADD THE TASK TO QUEUE 
      instance.Consumers.TryAdd(newTask.Id, newTask); 

      //SET IT AND FORGET IT 
      newTask.Start(); 



      return true; 
     } 
     else 
      return false; 
    } 
+0

你怎么知道它尚未完成? – 2014-09-05 20:20:52

+0

添加了详细信息。 (我知道发生这种情况的原因是因为我有另一种方法来监视instance.Consumers.Count - 在SomeAsyncMethod完成之前计数为0.) – 2014-09-05 20:21:20

+0

'SomeAsyncMethod'调用某些服务并更新数据库。全部通过'async'和'await'操作完成。 – 2014-09-05 20:22:29

SomeAsyncMethod,如果它的名称的任何迹象,是一种异步方法,大概一个返回一个Task。你正在创建一个新的任务start这个异步操作在另一个线程中。那Task会在你完成时返回开始的异步操作,而不是当它启动的异步操作完成时。

虽然你可以解开任务,但更简单的选择是不把它包裹在第一位。呼吁通过异步方法返回的Task延续:

SomeAsyncMethod().ContinueWith(t => 
{ 
    instance.Consumers.TryRemove(t.Id, out bogusTask); 
}); 
instance.Consumers.TryAdd(newTask.Id, newTask); 

当然,如果你希望能够用一个固定的平行度进行异步操作的一些数量,有更简单的方法。您可以使用SemaphoreSlim公平平凡创建任何固定的平行度的工作队列:

public class FixedParallelismQueue 
{ 
    private SemaphoreSlim semaphore; 
    public FixedParallelismQueue(int maxDegreesOfParallelism) 
    { 
     semaphore = new SemaphoreSlim(maxDegreesOfParallelism); 
    } 

    public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator) 
    { 
     await semaphore.WaitAsync(); 
     try 
     { 
      return await taskGenerator(); 
     } 
     finally 
     { 
      semaphore.Release(); 
     } 
    } 
    public async Task Enqueue(Func<Task> taskGenerator) 
    { 
     await semaphore.WaitAsync(); 
     try 
     { 
      await taskGenerator(); 
     } 
     finally 
     { 
      semaphore.Release(); 
     } 
    } 
} 
+0

啊!让我尝试一下。 – 2014-09-05 20:31:22

+0

SephamoreSlim块。 '当计数到零时,对其中一个Wait方法的后续调用会阻塞,直到其他线程释放信号为止。# – 2014-09-05 20:38:18

+2

@SimchaKhabinsky好东西我没有叫'Wait',那不是。这段代码不会阻塞,它完全是异步的。 – Servy 2014-09-05 20:39:03

由于SomeAsyncMethod是异步的,它完成了自己的任务前返回。

如果你有以上的SomeAsyncMethod代码控制,然后重构它是同步的(没有await/async),或者如果已经有一个非异步版本,那么就使用它。

如果你没有在该方法的代码控制,你可以等待它周围的任务,然后再继续完成:

Task newTask = new Task(()=>{ SomeAsyncMethod().Wait(); }); 
+1

解决方法是*不将它包装在任务中*不安排线程池线程启动任务,同步等待它,然后设置任务的结果。这太愚蠢了。 – Servy 2014-09-05 20:50:27

+0

我已经更新了我的答案。 – 2014-09-05 20:53:17

+1

@MarkCidade您修改后的建议同样不灵敏。为什么你会想要使用操作的同步版本,并创建一个新线程来坐在那里等待它,而不是使用操作的固有异步实现?它避免了坐在那里无所事事的需要。解决方案是不将任务包装在另一个任务中,而不是找到使异步代码同步的方法。 – Servy 2014-09-05 21:02:56