如何使任务更加独立
本文关键字:独立 任务 何使 | 更新日期: 2023-09-27 18:37:25
最初我使用Threads和ThreadPooling编写C#程序,但这里的大多数用户告诉我Async是提高效率的更好方法。我的程序将 JSON 对象发送到服务器,直到返回状态代码 200,然后继续执行下一个任务。
问题是,一旦我的一个任务检索到状态代码 200,它就会等待其他任务获取 200 代码,然后转到下一个任务。我希望每个任务继续它的下一个任务,而不是等待其他任务通过接收 200 响应来完成(或赶上)。
下面是并行运行任务的主要类。
public static void Main (string[] args)
{
var tasks = new List<Task> ();
for (int i = 0; i < 10; i++) {
tasks.Add (getItemAsync (i));
}
Task.WhenAny (tasks);
}
下面是 getItemAsync() 方法,它实际将信息发送到另一台服务器。在此方法起作用之前,它需要一个密钥。问题也就在这里,假设我运行 100 个任务,所有 100 个任务都会等到每个任务都有一个键。
public static async Task getItemAsync (int i)
{
if (key == "") {
await getProductKey (i).ConfigureAwait(false);
}
Uri url = new Uri ("http://www.website.com/");
var content = new FormUrlEncodedContent (new[] {
...
});
while (!success) {
using (HttpResponseMessage result = await client.PostAsync (url, content)) {
if (result.IsSuccessStatusCode) {
string resultContent = await result.Content.ReadAsStringAsync ().ConfigureAwait(false);
Console.WriteLine(resultContent);
success=true;
}
}
}
await doMoreAsync (i);
}
这是检索密钥的函数,它使用 HttpClient 并进行分析。
public static async Task getProductKey (int i)
{
string url = "http://www.website.com/key";
var stream = await client.GetStringAsync (url).ConfigureAwait(false);
var doc = new HtmlDocument ();
doc.LoadHtml (stream);
HtmlNode node = doc.DocumentNode.SelectNodes ("//input[@name='key']") [0];
try {
key = node.Attributes ["value"].Value;
} catch (Exception e) {
Console.WriteLine ("Exception: " + e);
}
}
在每个任务收到一个密钥并具有 200 状态代码后,它将运行 doMoreAsync()。我希望任何检索到 200 代码的单个任务都运行 doMoreAsync(),而无需等待其他任务赶上。我该怎么做?
您的 Main 方法不是等待任务完成,它只是触发一堆异步任务并返回。如果要等待异步任务,则只能从异步方法执行此操作。解决方法是从 Main 方法启动异步任务,并使用阻止Task.Wait()
等待其完成:
public static void Main(string[] args) {
Task.Run(async () =>
{
var tasks = new List<Task> ();
for (int i = 0; i < 10; i++) {
tasks.Add (getItemAsync (i));
}
var finishedTask = await Task.WhenAny(tasks); // This awaits one task
}).Wait();
}
从异步方法调用getItemAsync()
时,也可以删除ConfigureAwait(false)
。 ConfigureAwait(false)
只是确保某些代码不会在 UI 线程上执行。
如果要将另一个任务追加到任务,也可以使用 ContinueWith()
直接将其追加到上一个任务:
getItemAsync(i).ContinueWith(anotherTask);
您的主要问题似乎是您在所有并发运行的异步操作之间共享key
字段。这将不可避免地导致种族危险。相反,您应该更改getProductKey
方法,以返回每个检索到的密钥作为其异步操作的结果:
// ↓ result type of asynchronous operation
public static async Task<string> getProductKey(int i)
{
// retrieval logic here
// return key as result of asynchronous operation
return node.Attributes["value"].Value;
}
然后,你像这样消费它:
public static async Task getItemAsync(int i)
{
string key;
try
{
key = await getProductKey(i).ConfigureAwait(false);
}
catch
{
// handle exceptions
}
// use local variable 'key' in further asynchronous operations
}
最后,在主逻辑中,使用 WaitAll
来防止控制台应用程序在所有任务完成之前终止。尽管WaitAll
是阻塞调用,但在这种情况下是可以接受的,因为您希望主线程阻塞。
public static void Main (string[] args)
{
var tasks = new List<Task> ();
for (int i = 0; i < 10; i++) {
tasks.Add(getItemAsync(i));
}
Task.WaitAll(tasks.ToArray());
}