Task.WaitAll()在控制台应用程序中挂起

时间:2017-04-09 01:12:59

标签: c# asynchronous

我有一个控制台应用程序,我需要从4个不同的站点检索一些数据。我将每个HTTP请求放在一个任务中,然后等待它们全部完成。

当我只需要从2个站点获取数据时,它正在工作。但后来我需要添加其他数据源,当添加3个或更多请求时,Task.WaitAll()会挂起。

以下是我的代码。

我最终使用Task.WaitAll()的原因是因为我需要停止并阻止控制台应用程序退出 - 即只有在所有HTTP请求返回数据后才需要执行其他任务。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static Task[] tasks = new Task[3];

        static void Main(string[] args)
        {
            try
            {
                Run();
            }
            catch (System.Exception ex)
            {

            }
        }

        public static async void Run()
        {
            //works when using one or two tasks
            tasks[0] = HttpExtensions.GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt"); 
            tasks[1] = HttpExtensions.GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt");

            //fails when add 3 or more task
            tasks[2] = HttpExtensions.GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt"); 
            //tasks[3] = HttpExtensions.GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt"); 

            Task.WaitAll(tasks);

            var result4 = ((Task<Stream>)tasks[2]).Result;

        }
    }

    public static class HttpExtensions
    {
        public static Stopwatch sw;
        public static long http_ticks = 0;

        public static Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request)
        {
            var taskComplete = new TaskCompletionSource<HttpWebResponse>();
            request.BeginGetResponse(asyncResponse =>
            {
                try
                {
                    HttpWebRequest responseRequest = (HttpWebRequest)asyncResponse.AsyncState;
                    HttpWebResponse someResponse = (HttpWebResponse)responseRequest.EndGetResponse(asyncResponse);
                    taskComplete.TrySetResult(someResponse);
                }
                catch (WebException webExc)
                {
                    HttpWebResponse failedResponse = (HttpWebResponse)webExc.Response;
                    taskComplete.TrySetResult(failedResponse);
                }
            }, request);
            return taskComplete.Task;
        }


        public static async Task<Stream> GetMyData(string urlToCall)
        {

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(urlToCall);
            request.Method = HttpMethod.Get;
            HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
            //using (var sr = new StreamReader(response.GetResponseStream()))
            //{

            return response.GetResponseStream();
            //}
        }

    }

    public static class HttpMethod
    {
        public static string Head { get { return "HEAD"; } }
        public static string Post { get { return "POST"; } }
        public static string Put { get { return "PUT"; } }
        public static string Get { get { return "GET"; } }
        public static string Delete { get { return "DELETE"; } }
        public static string Trace { get { return "TRACE"; } }
        public static string Options { get { return "OPTIONS"; } }
        public static string Connect { get { return "CONNECT"; } }
        public static string Patch { get { return "PATCH"; } }
    }
}

2 个答案:

答案 0 :(得分:2)

有许多问题。

首先,正如我在上面的评论中提到的,如果不返回Task,您或多或少会挂起您的应用程序,因为它无法判断任务何时完成。

但是,一旦您更改Run()方法以返回任务,您需要通过Main方法中的Task.Run调用来调用它。

其次,使用WebClient使代码过于复杂。切换到HttpClient并利用其自然的异步/等待API。

第三,你实际上并没有等待Run()方法中的任何内容,所以将它更改为任务什么都不做,因为你没有等待导致它同步运行的结果(没有双关语意)。更新您的方法以等待结果。

最后,WaitAll阻止线程,这可能不是你想要的。您可以使用WhenAll代替该调用,允许您的应用程序在您的任务运行时释放该线程。

以下是我推荐的修改的完整工作示例,简化为显示工作程序。 Main方法建议来自https://social.msdn.microsoft.com/Forums/vstudio/en-US/fe9acdfc-66cd-4b43-9460-a8053ca51885/using-new-asyncawait-in-console-app?forum=netfxbcl

class Program
{
    static Task[] tasks = new Task[3];
    static HttpClient _client = new HttpClient();

    static void Main(string[] args)
    {
        Console.WriteLine("Main start");
        Task t = Run();
        t.ContinueWith((str) =>
        {
            Console.WriteLine(str.Status.ToString());
            Console.WriteLine("Main end");
        });
        t.Wait();
    }

    public static async Task Run()
    {
        tasks[0] = GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt");
        tasks[1] = GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt");
        tasks[2] = GetMyData("http://www.w3.org/TR/PNG/iso_8859-1.txt");

        await Task.WhenAll(tasks);

        var result4 = (await (Task<Stream>)tasks[2]);
    }

    public static async Task<Stream> GetMyData(string urlToCall)
    {
        return await _client.GetStreamAsync(urlToCall);
    }
}

答案 1 :(得分:0)

我认为问题更多的是理解Task和异步等待;我可能错了,所以在前面道歉。

Task是一个进入线程池的托管线程。任务有一个类型为T.的Task.Result

您可以创建一个任务,然后启动它,然后等待它。 (从来没有一个好主意开始,然后立即等待任务,但要理解......)

var task = new Task(() => DoWork());
task.Start();
task.Wait();

该任务将在新线程中执行DoWork()方法。 调用线程将在task.Wait();

处进行BLOCK

您还可以为任务提供一个ContinueWith Action,它将执行调用线程的剩余工作。

var task = new Task(() => DoWorkOnNewThread());
task.ContinueWith(() => MainThreadWork());
task.Start(); //Notice no more task.Wait();

所以,如果你关注那么一点,那么就可以正确地使用异步等待。

async关键字告诉编译器在到达await关键字WHERE返回GetAwaiter()之后包装所有重新映射的代码。这很重要,因为在您实际创建任务(最好也是启动)并返回它之前,您没有GetAwaiter();

private Task DoWorkAsync()
{
    var task = new Task(() => DoWork());
    task.Start();
    return task;
}

private async void Method()
{
     //Main thread code...
     await DoWorkAsync(); //Returns to whoever called Method()
     //More main thread code to be QUEUED to run AFTER DoWorkAsync is complete.
     //This portion of code, when compiled, is essentially wrapped in the ContinueWith(...
}

因此,如果您仍然继续关注,那么这里就是踢球者。你返回同一个线程直到你返回一个GetAwaiter(),它只能在一个Task中找到。如果任务从未启动过,那么您将在技术上永远等待该任务。所以这里有一些评论显示了线程转换。

private Task DoWorkAsync()
{
    Debug.WriteLine("Still on main thread")
    var task = new Task(() =>
    {
        Debug.WriteLine("On background thread");
    });
    task.Start(); //On main thread.
    return task; //On main thread.
}

private async void Method()
{
     Debug.WriteLine("On main thread");
     await DoWorkAsync(); //returns to caller after DoWorkAsync returns Task
     Debug.WriteLine("Back on main thread"); //Works here after the task DoWorkAsync returned is complete
}

返回正在运行的任务的一种更简单的方法是返回Task.Run(()=&gt; DoWork());如果你看一下Run的返回值,那就是Task,那个任务已经启动了。

请原谅我,如果这不是你想要的,但我觉得使用async等待正确的混淆比你的代码混乱更多。我可能错了,但我觉得如果你能够更多地了解任务本身以及异步等待是如何工作的,你会看到你的问题。如果这不是您正在寻找的内容,我将删除答案。