如何使任务更加独立

本文关键字:独立 任务 何使 | 更新日期: 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());
}