发送HttpClient请求时,使用async / await的代码失败

时间:2017-10-02 23:13:07

标签: c# .net winforms asynchronous async-await

我有一个有两个角色的WinForms应用程序。如果不存在命令行参数,则Main函数调用Application.Run,​​并显示UI。如果存在命令行参数,则不调用Application.Run。相反,我调用这样的异步方法:

result = HandleCommandLine(args).GetAwaiter().GetResult();

(我是async / await的新手,此表格基于SO答案)。

最终目标是遍历列表,并为每个条目启动一个新任务。这些任务中的每一项都应与其他任务并行运行。任务开始如下:

runningTasks.Add(Task.Factory.StartNew((args) => HandlePlayback( (Dictionary<string,string>) ((object[])args)[0]), new object[] { runArgs } ));

任务被添加到runningTasks的集合中,我稍后调用:

Task.WaitAll(runningTasks.ToArray());

在每个runningTasks中,我正在尝试使用HttpClient发送Web请求:

using (HttpResponseMessage response = await Client.SendAsync(message))
{
    using (HttpContent responseContent = response.Content)
    {
        result = await responseContent.ReadAsStringAsync();
    }
}

一旦调用Client.SendAsync,整个事情就会变得糟透了。我的所有runningTasks都完成了,应用程序退出了。在任何这些任务中都没有经过Client.SendAsync执行任何操作。

由于我是async / await的新手,因此我对完全可能出错的内容几乎没有什么想法,因此很少有关于如何修复它的想法。我想这与在这种情况下的SynchronizationContexts有关(WinForms应用程序就像一个控制台应用程序),但我没有理解我需要做什么以及在哪里保持服务请求和Web请求异步调用来自导致一切都过早完成。

我想我的问题是,为什么(只有一些)等待&#39;等等。调用导致所有任务完成?我该怎么办呢?

更新

两件事。 @Joe White:无论我在哪里检查,WindowsFormsSynchronizationContext.Current都是null。

@David Pine:Minimal(种类:))完整可行的例子如下。您将需要向项目添加命令行参数,或强制执行HandleCommandLine函数。在此示例中,它尝试为三个站点中的每个站点发出网站请求。如果它们存在,它似乎并不重要。代码到达Client.SendAsync一定次数(通常不是三次),但时间似乎很重要。

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    static class Program
    {
        static List<Task> runningTasks = new List<Task>();

        [STAThread]
        static int Main()
        {
            int result = 1; // true, optimism

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            string[] args = Environment.GetCommandLineArgs();

            if (args.Length > 1)
            {
                // do the command line work async, while keeping this thread active
                result = HandleCommandLine(args).GetAwaiter().GetResult();
            }
            else
            {
                // normal interface mode
                Application.Run(new Form1());
            }

            return result;
        }


        static async Task<int> HandleCommandLine(string[] args)
        {
            // headless mode

            int result = 1; // true, optimism

            result = await HandleControlMode(args);


            return result;
        }

        private static async Task<int> HandleControlMode(string[] Arguments)
        {
            int result = 1; // optimism

            try
            {
                List<string> sites = new List<string>() { @"http://localhost/site1", @"http://localhost/site2", @"http://localhost/site3" };
                foreach (string site in sites)
                {
                    Begin(site);    // fire off tasks

                    // the HandleControlMode method is async because in other circumstances, I do the following:
                    //await Task.Delay(5000); // sleep 5 seconds

                }

                // wait while all test running threads complete
                try
                {
                    Task.WaitAll(runningTasks.ToArray());
                }
                catch (Exception)
                {
                    // not really a catch all handler...
                }
            }
            catch (Exception)
            {
                // not really a catch all handler...
            }

            return result;

        }

        private static void Begin(string site)
        {
            //runningTasks.Add(Task.Factory.StartNew(() => HandlePlayback(runArgs)));
            runningTasks.Add(Task.Factory.StartNew((args) => HandlePlayback((string)((object[])args)[0]), new object[] { site }));
        }

        private static async Task<int> HandlePlayback(string site)
        {
            int result = 1;

            try
            {
                PlaybackEngine engine = new PlaybackEngine(site);
                bool runResult = await engine.RunCommandLine(site);

                if (!runResult)
                {
                    result = 0;
                }
            }
            catch (Exception)
            {
                result = 0;
            }

            return result;
        }
    }

    public class PlaybackEngine
    {
        private static HttpClientHandler ClientHandler = new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            AutomaticDecompression = System.Net.DecompressionMethods.GZip | DecompressionMethods.Deflate
        };
        private static HttpClient Client = new HttpClient(ClientHandler);

        public string Target { get; set; }

        public PlaybackEngine(string target)
        {
            Target = target;
        }

        public async Task<bool> RunCommandLine(string site)
        {
            bool success = true;
            string response = await this.SendRequest();
            return success;
        }

        private async Task<string> SendRequest()
        {
            string result = string.Empty;

            string requestTarget = Target;

            HttpMethod method = HttpMethod.Post;
            var message = new HttpRequestMessage(method, requestTarget);

            StringContent requestContent = null;
            requestContent = new StringContent("dummycontent", Encoding.UTF8, "application/x-www-form-urlencoded");

            message.Content = requestContent;

            try
            {
                using (HttpResponseMessage response = await Client.SendAsync(message))
                {
                    using (HttpContent responseContent = response.Content)
                    {
                        result = await responseContent.ReadAsStringAsync();
                        System.Diagnostics.Debug.WriteLine(result);
                    }
                }

            }
            catch (Exception ex)
            {

            }

            return result;
        }
    }
}

UPDATE2: 我在http://rextester.com/CJS33330

上放了类似的代码

它是一个直接的控制台应用程序,我已经添加了.ConfigureAwait(false)等待所有等待(没有效果)。在单独的测试中,我尝试了4或5种其他方法从Main调用第一个异步函数 - 这些函数都有效但行为相同。

1 个答案:

答案 0 :(得分:0)

这段代码的问题在于,我不是在等待我认为的那些任务。 runningTasks集合接受任何类型的任务。我没有意识到Task.Factory.StartNew返回的类型不同于我试图启动的任务。我的函数返回

Task<int>

但StartNew返回

Task<Task<int>>

这些任务立即完成,因此主线程没有保持足够长的时间来运行实际例程。你必须等待内部任务:

Task<Task<int>> wrappedTask = Task.Factory.StartNew(...);
Task<int> t = await wrappedTask;
runningTasks.Add(t);

...

Task allTasks = Task.WhenAll(runningTasks.ToArray());
await allTasks;

出于某种原因,我无法使用内置的&#34; .Unwrap&#34;应该是等效的函数,但上面的代码完成了这项工作。