在一次设置最大运行任务时等待多个异步任务

时间:2014-04-26 20:58:16

标签: c# multithreading asynchronous async-await task-parallel-library

所以我开始尝试理解async,Task,lambda等等,而我无法让它像我想的那样工作。使用下面的代码我希望它能锁定btnDoWebRequest,将未知数量的WebRequests作为任务执行,并且一旦完成所有任务,就解锁btnDoWebRequest。但是我只希望一次运行最多3个或任何数量的任务,我部分来自Have a set of Tasks with only X running at a time

但是在以多种方式尝试和修改我的代码后,它将始终立即跳回并重新启用btnDoWebRequest。当然VS正在警告我需要等待,目前在" .ContinueWith((任务)"以及在#34中的async;等待Task.WhenAll(requestInfoList .Select(async i =>&) #34;,但似乎无法在哪里或如何投入所需的等待。当然,因为我还在学习,我很有可能在这方面做错了所有事情都需要我会非常感激任何帮助。

谢谢

    private SemaphoreSlim maxThread = new SemaphoreSlim(3);
    private void btnDoWebRequest_Click(object sender, EventArgs e)
    {
        btnDoWebRequest.Enabled = false;
        Task.Factory.StartNew(async () => await DoWebRequest()).Wait();
        btnDoWebRequest.Enabled = true;
    }

    private async Task DoWebRequest()
    {
        List<requestInfo> requestInfoList = new List<requestInfo>();
        for (int i = 0; dataRequestInfo.RowCount - 1 > i; i++)
        {
            requestInfoList.Add((requestInfo)dataRequestInfo.Rows[i].Tag);
        }
        await Task.WhenAll(requestInfoList .Select(async i => 
        {
            maxThread.Wait();
            Task.Factory.StartNew(() =>
            {
                var task = Global.webRequestWork(i);
            }, TaskCreationOptions.LongRunning).ContinueWith((task) => maxThread.Release());
        }));
    }

4 个答案:

答案 0 :(得分:10)

首先,默认情况下不要使用Task.Factory.StartNew。实际上,应该在async代码中避免这种情况。如果需要在后台线程上执行代码,请使用Task.Run

在您的情况下,无需使用Task.Run(或Task.Factory.StartNew)。

从最低级别开始,逐步提升。您已经拥有异步网络请求方法,我将其重命名为WebRequestAsync以遵循Task-based Asynchronous Programming命名准则。

接下来,使用SemaphoreSlim上的异步 API来限制它:

await maxThread.WaitAsync();
try
{
  await Global.WebRequestWorkAsync(i);
}
finally
{
  maxThread.Release();
}

为每个请求信息执行此操作(请注意,不需要后台线程):

private async Task DoWebRequestsAsync()
{
  List<requestInfo> requestInfoList = new List<requestInfo>();
  for (int i = 0; dataRequestInfo.RowCount - 1 > i; i++)
  {
    requestInfoList.Add((requestInfo)dataRequestInfo.Rows[i].Tag);
  }
  await Task.WhenAll(requestInfoList.Select(async i => 
  {
    await maxThread.WaitAsync();
    try
    {
      await Global.WebRequestWorkAsync(i);
    }
    finally
    {
      maxThread.Release();
    }
  }));
}

最后,从你的UI调用它(同样,不需要后台线程):

private async void btnDoWebRequest_Click(object sender, EventArgs e)
{
  btnDoWebRequest.Enabled = false;
  await DoWebRequestsAsync();
  btnDoWebRequest.Enabled = true;
}

总之,只在需要时才使用Task.Run;不要使用Task.Factory.StartNew,也不要使用Wait(请改用await)。我的博客上有一个async intro,其中包含更多信息。

答案 1 :(得分:1)

您的代码存在一些问题:

  1. 在Task上使用Wait()就像是同步运行一样,因此只有在完成所有操作并重新启用按钮时,您才会注意到UI会做出反应。您需要等待异步方法才能真正运行异步。更重要的是,如果一个方法像Web请求那样进行IO绑定工作,那么启动新的线程池线程(使用Task.Factory.StartNew)是多余的,并且浪费资源。

  2. 您的按钮点击事件处理程序需要标记为异步,以便您可以在方法内等待。

  3. 为了清晰起见,我已经清理了一些代码,使用新的SemaphoreSlim WaitAsync并将您的for替换为LINQ查询。您只能将前两个点应用到您的代码中。

    private SemaphoreSlim maxThread = new  SemaphoreSlim(3);
    
    private async void btnDoWebRequest_Click(object  sender, EventArgs e)
    {
        btnDoWebRequest.Enabled = false;
        await DoWebRequest();
        btnDoWebRequest.Enabled = true;
     }
    
     private async Task DoWebRequest()
     {
         List<requestInfo> requestInfoList = new List<requestInfo>();
    
         var requestInfoList =  dataRequestInfo.Rows.Select(x => x.Tag).Cast<requestInfo>();
    
        var tasks = requestInfoList.Select(async I => 
        {
             await maxThread.WaitAsync();
             try
             {
                 await Global.webRequestWork(i);
             }
             finally
             {
                 maxThread.Release();
             }
       });
    
       await Task.WhenAll(tasks);
    

答案 2 :(得分:0)

我为此创建了一个扩展方法。

可以像这样使用:

var tt = new List<Func<Task>>()
{
    () => Thread.Sleep(300), //Thread.Sleep can be replaced by your own functionality, like calling the website
    () => Thread.Sleep(800),
    () => Thread.Sleep(250),
    () => Thread.Sleep(1000),
    () => Thread.Sleep(100),
    () => Thread.Sleep(200),
};
await tt.WhenAll(3); //this will let 3 threads run, if one ends, the next will start, untill all are finished.

扩展方法:

public static class TaskExtension
{
    public static async Task WhenAll(this List<Func<Task>> actions, int threadCount)
    {
        var _countdownEvent = new CountdownEvent(actions.Count);
        var _throttler = new SemaphoreSlim(threadCount);

        foreach (Func<Task> action in actions)
        {
            await _throttler.WaitAsync();

            Task.Run(async () =>
            {
                try
                {
                    await action();
                }
                finally
                {
                    _throttler.Release();
                    _countdownEvent.Signal();
                }
            });
        }

        _countdownEvent.Wait();
    }
}

答案 3 :(得分:0)

我们可以使用SemaphoreSlim轻松实现这一目标。扩展方法I已创建:

    /// <summary>
    /// Concurrently Executes async actions for each item of <see cref="IEnumerable<typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type of IEnumerable</typeparam>
    /// <param name="enumerable">instance of <see cref="IEnumerable<typeparamref name="T"/>"/></param>
    /// <param name="action">an async <see cref="Action" /> to execute</param>
    /// <param name="maxActionsToRunInParallel">Optional, max numbers of the actions to run in parallel,
    /// Must be grater than 0</param>
    /// <returns>A Task representing an async operation</returns>
    /// <exception cref="ArgumentOutOfRangeException">If the maxActionsToRunInParallel is less than 1</exception>
    public static async Task ForEachAsyncConcurrent<T>(
        this IEnumerable<T> enumerable,
        Func<T, Task> action,
        int? maxActionsToRunInParallel = null)
    {
        if (maxActionsToRunInParallel.HasValue)
        {
            using (var semaphoreSlim = new SemaphoreSlim(
                maxActionsToRunInParallel.Value, maxActionsToRunInParallel.Value))
            {
                var tasksWithThrottler = new List<Task>();

                foreach (var item in enumerable)
                {
                    // Increment the number of currently running tasks and wait if they are more than limit.
                    await semaphoreSlim.WaitAsync();

                    tasksWithThrottler.Add(Task.Run(async () =>
                    {
                        await action(item);

                        // action is completed, so decrement the number of currently running tasks
                        semaphoreSlim.Release();
                    }));
                }

                // Wait for all tasks to complete.
                await Task.WhenAll(tasksWithThrottler.ToArray());
            }
        }
        else
        {
            await Task.WhenAll(enumerable.Select(item => action(item)));
        }
    }

样本用法:

await enumerable.ForEachAsyncConcurrent(
    async item =>
    {
        await SomeAsyncMethod(item);
    },
    5);