所以我开始尝试理解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());
}));
}
答案 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)
您的代码存在一些问题:
在Task上使用Wait()就像是同步运行一样,因此只有在完成所有操作并重新启用按钮时,您才会注意到UI会做出反应。您需要等待异步方法才能真正运行异步。更重要的是,如果一个方法像Web请求那样进行IO绑定工作,那么启动新的线程池线程(使用Task.Factory.StartNew)是多余的,并且浪费资源。
您的按钮点击事件处理程序需要标记为异步,以便您可以在方法内等待。
为了清晰起见,我已经清理了一些代码,使用新的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);