如何使任务更加独立?

时间:2016-03-05 10:23:06

标签: c# .net multithreading asynchronous task

最初我使用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代码的任何单个Task都能运行doMoreAsync()而无需等待其他任务赶上。我该怎么做?

2 个答案:

答案 0 :(得分:0)

您的主要方法不是等待任务完成,它只会触发一堆异步任务并返回。 如果要等待异步任务,则只能通过异步方法执行此操作。解决方法是从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);

答案 1 :(得分:0)

您的主要问题似乎是您在所有同时运行的异步操作中共享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());
}