简体   繁体   中英

Multiple worker threads vs One worker with async/await

My code currently has the following 10 worker threads. Each worker thread continues polling a job from the queue and then process the long running job.

for (int k=0; k<10; k++)
{
  Task.Factory.StartNew(() => DoPollingThenWork(), TaskCreationOptions.LongRunning);
}

void DoPollingThenWork()
{
  while (true)
  {
    var msg = Poll();
    if (msg != null)
    {
      Thread.Sleep(3000); // process the I/O bound job
    }
  }
}

I am refactoring the underlying code to use async/await pattern. I think I can rewrite the above code to the followings. It uses one main thread that keeps creating the async task, and use SemaphoreSlim to throttle the number of concurrent tasks to 10.

Task.Factory.StartNew(() => WorkerMainAsync(), TaskCreationOptions.LongRunning);

async Task WorkerMainAsync()
{
  SemaphoreSlim ss = new SemaphoreSlim(10);
  while (true)
  {
    await ss.WaitAsync();
    Task.Run(async () => 
              {
                await DoPollingThenWorkAsync();
                ss.Release();
              });
  }
}

async Task DoPollingThenWorkAsync()
{
  var msg = Poll();
  if (msg != null)
  {
    await Task.Delay(3000); // process the I/O-bound job
  }
}

Both should behave the same. But I think the second options seems better because it doesn't block the thread. But the downside is I can't do Wait (to gracefully stop the task) since the task is like fire and forget. Is the second option the right way to replace the traditional worker threads pattern?

When you have code that's asynchronous, you usually have no reason to use Task.Run() (or, even worse, Task.Factory.StartNew() ). This means that you can change your code to something like this:

await WorkerMainAsync();

async Task WorkerMainAsync()
{
  SemaphoreSlim ss = new SemaphoreSlim(10);
  while (true)
  {
    await ss.WaitAsync();
    // you should probably store this task somewhere and then await it
    var task = DoPollingThenWorkAsync();
  }
}

async Task DoPollingThenWorkAsync(SemaphoreSlim semaphore)
{
  var msg = Poll();
  if (msg != null)
  {
    await Task.Delay(3000); // process the I/O-bound job
  }

  // this assumes you don't have to worry about exceptions
  // otherwise consider try-finally
  semaphore.Release();
}

Usually you don't use async/await inside a CPU-bound task. The method that starts such a task ( WorkerMainAsync ) can use async/await , but you should be tracking pending tasks:

async Task WorkerMainAsync()
{
  SemaphoreSlim ss = new SemaphoreSlim(10);
  List<Task> trackedTasks = new List<Task>();
  while (DoMore())
  {
    await ss.WaitAsync();
    trackedTasks.Add(Task.Run(() => 
              {
                DoPollingThenWorkAsync();
                ss.Release();
              }));
  }
  await Task.WhenAll(trackedTasks);
}

void DoPollingThenWorkAsync()
{
  var msg = Poll();
  if (msg != null)
  {
    Thread.Sleep(2000); // process the long running CPU-bound job
  }
}

Another exercise would be to remove tasks from trackedTasks as they are finishing. For example, you could use ContinueWith to remove a finished tasks (in this case, remember to use lock to protect trackedTasks from simultaneous access).

If you really need to use await inside DoPollingThenWorkAsync , the code wouldn't change a lot:

    trackedTasks.Add(Task.Run(async () => 
              {
                await DoPollingThenWorkAsync();
                ss.Release();
              }));

Note that in this case, you'd be dealing with a nested task here for the async lambda, which Task.Run will automatically unwrap for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM