异步循环不再有可用的HttpContext

本文关键字:HttpContext 循环 不再 异步 | 更新日期: 2023-09-27 18:29:33

我有一个要求,就是处理X个文件,通常我们每天可以收到大约100个文件,是一个zip文件,所以我必须打开它,创建一个流,然后将它发送到WebApi服务,这是一个工作流,这个工作流又调用了两个WebApi步骤。

我实现了一个控制台应用程序,它在文件中循环,然后调用一个包装器,该包装器使用HttpWebRequest.GetResponse().进行REST调用

我强调测试了该解决方案并创建了11K文件,在同步版本中,处理所有文件大约需要17分钟,但我想创建一个异步版本,并能够使用awaitHttpWebRequest.GetResponseAsync().

这是异步版本:

private async Task<KeyValuePair<HttpStatusCode, string>> REST_CallAsync(
        string httpMethod,
        string url,
        string contentType,
        object bodyMessage = null,
        Dictionary<string, object> headerParameters = null,
        object[] queryStringParamaters = null,
        string requestData = "")
    {
        try
        {
            HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create("some url");
            req.Method = "POST";
            req.ContentType = contentType;
                //Adding zip stream to body
                var reqBodyBytes = ReadFully((Stream)bodyMessage);
                req.ContentLength = reqBodyBytes.Length;
                Stream reqStream = req.GetRequestStream();
                reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);
                reqStream.Close();                
            //Async call
            var resp = await req.GetResponseAsync();
            var httpResponse = (HttpWebResponse)resp as HttpWebResponse;
            var responseData = new StreamReader(resp.GetResponseStream()).ReadToEnd();                
            return new KeyValuePair<HttpStatusCode,string>(httpResponse.StatusCode, responseData);
        }
        catch (WebException webEx)
        {
        //something
        }
        catch (Exception ex)
        {
        //something
        }

在我的控制台应用程序中,我有一个循环要打开并调用异步(CallServiceAsync在封面下调用上面的方法)

foreach (var zipFile in Directory.EnumerateFiles(directory))
                                {
                                        using (var zipStream = System.IO.File.OpenRead(zipFile))
                                        {
                                            await _restFulService.CallServiceAsync<WorkflowResponse>(
                                            zipStream,
                                            headerParameters,
                                            null,
                                            true);
                                        }
                                        processId++;
                                    }
                                }

最终发生的是,11K中只有2K得到了处理,没有抛出任何异常,所以我一无所知,所以我将我调用异步的版本改为:

foreach (var zipFile in Directory.EnumerateFiles(directory))
                                {      
                                        using (var zipStream = System.IO.File.OpenRead(zipFile))
                                        {
                                            tasks.Add(_restFulService.CallServiceAsync<WorkflowResponse>(
                                            zipStream,
                                            headerParameters,
                                            null,
                                            true));
                                        }
                                    }
                                }

还有另一个循环等待任务:

foreach (var task in await System.Threading.Tasks.Task.WhenAll(tasks))
                {
                    if (task.Value != null)
                    {
                        Console.WriteLine("Ending Process");
                    }
                }

现在我面临一个不同的错误,当我处理三个文件时,第三个文件收到:

客户端已断开连接,因为基础请求已完成。HttpContext不再可用。

我的问题是,我在这里做错了什么?我使用SimpleInjector作为IoC,这会是问题吗?

当你这样做的时候,WhenAll是在等待每个线程运行吗?不是让它同步,所以它等待线程完成以执行下一个线程?我是这个异步世界的新手,所以任何帮助都将不胜感激。

异步循环不再有可用的HttpContext

对于那些在我的问题中添加了-1,而不是提供某种类型的解决方案,只是建议了一些没有意义的东西的人来说,这就是答案,也是为什么指定尽可能多的细节是有用的原因。

第一个问题是,由于我使用的是IIS Express,如果我没有运行解决方案(F5),那么web应用程序就不可用,这种情况有时并不总是发生在我身上。

第二个问题,也是让我非常头疼的一个问题是,并不是所有的文件都得到了处理,我以前应该知道这个问题的原因,那就是在控制台应用程序中使用异步等待。我通过以下操作强迫我的控制台应用程序使用异步:

static void Main(string[] args)
    {
        System.Threading.Tasks.Task.Run(() => MainAsync(args)).Wait();
    }
    static async void MainAsync(string[] args)
    {
      //rest of code

然后,如果你注意到我的foreach中有await关键字,那么从概念上讲,await会将控制流发送回调用者,在这种情况下,操作系统是调用控制台应用程序的操作系统(这就是为什么在控制台应用程序中使用async-await没有太大意义,我这样做是因为我错误地通过调用async方法使用了await)。所以结果是我的过程只处理了一些X数量的文件,所以我最终要做的是:

添加一个任务列表,就像我上面做的一样:

tasks.Add(_restFulService.CallServiceAsync<WorkflowResponse>(....

运行线程的方法是(在我的控制台应用程序中):

ExecuteAsync(tasks);

最后我的方法:

static void ExecuteAsync(List<System.Threading.Tasks.Task<KeyValuePair<HttpStatusCode, WorkflowResponse>>> tasks)
    {
         System.Threading.Tasks.Task.WhenAll(tasks).Wait();
    }

更新:根据Scott的反馈,我改变了执行线程的方式。

现在我可以处理我的所有文件了,我测试了它,在同步过程中处理1000个文件花了大约160多秒来运行所有过程(我有三个步骤的工作流程来处理文件),当我把异步过程放在适当的位置时,花了80多秒,所以几乎一半的时间。在我的IIS生产服务器中,我相信执行时间会更短。

希望这对任何面临这类问题的人都有帮助。