简体   繁体   English

从任务继续写入ASP.NET响应输出流

[英]Write in ASP.NET response output stream from task continuation

I have a http handler which should write to output some text. 我有一个http处理程序,应该写入输出一些文本。 Text content is retrieved asynchronously, so I want to write to response stream in the ProcessRequest method like this: 文本内容是异步检索的,所以我想在ProcessRequest方法中写入响应流,如下所示:

GetContent().ContinueWith(task => 
{
    using (var stream = task.Result)
    {
        stream.WriteTo(context.Response.OutputStream);
    }
});

However, I get a NullReferenceException with the stack trace 但是,我得到一个带有堆栈跟踪的NullReferenceException

in System.Web.HttpWriter.BufferData(Byte[] data, Int32 offset, Int32 size, Boolean needToCopyData)
   in System.Web.HttpWriter.WriteFromStream(Byte[] data, Int32 offset, Int32 size)
   in System.Web.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   in System.IO.MemoryStream.WriteTo(Stream stream)
   in SomeHandler.<>c__DisplayClass1.<ProcessRequest>b__0(Task`1 task) in SomeHandler.cs:line 33
   in System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
   in System.Threading.Tasks.Task.Execute()

If I do not use ContinueWith and write response after task.Wait() - there are no errors, but, obviously it is not a solution. 如果我在task.Wait()之后不使用ContinueWith并写入响应 - 没有错误,但显然它不是解决方案。

var task = GetContent();
task.Wait();
using (var stream = task.Result)
{
    stream.WriteTo(context.Response.OutputStream);
}

How can I eliminate this error? 如何消除此错误? (.net 4.0 is used) (.net 4.0使用)

You need to implement IHttpAsyncHandler . 您需要实现IHttpAsyncHandler Check "Walkthrough: Creating an Asynchronous HTTP Handler" . 检查“演练:创建异步HTTP处理程序”

On top of that, you can use async/await to copy the stream (note CopyAsync below). 最重要的是,您可以使用async/await复制流(请注意下面的CopyAsync )。 To be able to use async/await and target .NET 4.0 with VS2012+, add Microsoft.Bcl.Async package to your project. 为了能够使用async/await并使用VS2012 +定位.NET 4.0,请将Microsoft.Bcl.Async包添加到项目中。

This way, no threads are unnecessary blocked. 这样,没有线程被不必要阻止。 A complete example (untested): 一个完整的例子(未经测试):

public partial class AsyncHandler : IHttpAsyncHandler
{
    async Task CopyAsync(HttpContext context)
    {
        using (var stream = await GetContentAsync(context))
        {
            await stream.CopyToAsync(context.Response.OutputStream);
        }
    }

    #region IHttpAsyncHandler
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        return new AsyncResult(cb, extraData, CopyAsync(context));
    }

    public void EndProcessRequest(IAsyncResult result)
    {
        // at this point, the task has compeleted
        // we use Wait() only to re-throw any errors
        ((AsyncResult)result).Task.Wait();
    }

    public bool IsReusable
    {
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        throw new NotImplementedException();
    }
    #endregion

    #region AsyncResult
    class AsyncResult : IAsyncResult
    {
        object _state;
        Task _task;
        bool _completedSynchronously;

        public AsyncResult(AsyncCallback callback, object state, Task task)
        {
            _state = state;
            _task = task;
            _completedSynchronously = _task.IsCompleted;
            _task.ContinueWith(t => callback(this), TaskContinuationOptions.ExecuteSynchronously);
        }

        public Task Task
        {
            get { return _task; }
        }

        #region IAsyncResult
        public object AsyncState
        {
            get { return _state; }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return ((IAsyncResult)_task).AsyncWaitHandle; }
        }

        public bool CompletedSynchronously
        {
            get { return _completedSynchronously; }
        }

        public bool IsCompleted
        {
            get { return _task.IsCompleted; }
        }
        #endregion
    }
    #endregion
}

This problem could be based on the "context-switch" which happens when the task is executed in its own thread. 此问题可能基于“上下文切换”,该任务在其自己的线程中执行时发生。

The current HttpContext is only available in the requests thread. 当前的HttpContext仅在请求线程中可用。 You could however make a "local reference" to be able to access it (but that would not completely solve your problem - see below): 但是,你可以制作一个“本地参考”来访问它(但这不能完全解决你的问题 - 见下文):

var outputStream = context.Response.OutputStream;
GetContent().ContinueWith(task => 
{
    using (var stream = task.Result)
    {
        stream.WriteTo(outputStream);
    }
});

The problem now is, that your request is very likely already done when the ContinueWith is executed so your stream to the client is already closed. 现在的问题是,当执行ContinueWith时,您的请求很可能已经完成,因此您的客户端流已经关闭。
You need to write your content to the stream before this happens. 在此之前,您需要将内容写入流。

I need to strike the follwoing part of my answer. 我需要完成我的答案的下一部分。 Http handlers are not async -capable in 4.0 as this neets the HttpTaskAsyncHandler base-class which is not available before 4.5 => I don't know a way around the Wait in the ProcessRequest method in 4.0 when using a http handler: Http处理程序在4.0中不是async可用的,因为它支持HttpTaskAsyncHandler基类,这在4.5之前是不可用的=>当使用http处理程序时,我不知道4.0中ProcessRequest方法中的Wait的方法:

I recommend using something like the following: 我建议使用以下内容:

 
 
 
  
  using(var result = await GetContent()) { stream.WriteTo(context.Response.OutputStream); }
 
  

and the appropriate NuGet-Package for await . 以及适当的 NuGet-Package await

Quite recently, I had come across the similar task to write an ashx handler to put response asynchronously. 最近,我遇到了类似的任务,写了一个ashx处理程序来异步地放置响应。 The purpose was to generate some big string, perform I/O and return it back on the response stream, and thus benchmark ASP.NET with other languages like node.js for a heavily I/O bound app I'm going to develop. 目的是生成一些大字符串,执行I / O并将其返回到响应流上,从而使用其他语言(如node.js)对ASP.NET进行基准测试,以构建我将要开发的大量I / O绑定应用程序。 This is what I ended up doing (it works): 这就是我最终做的事情(它有效):

    private StringBuilder payload = null;

    private async void processAsync()
    {
        var r = new Random (DateTime.Now.Ticks.GetHashCode());

        //generate a random string of 108kb
        payload=new StringBuilder();
        for (var i = 0; i < 54000; i++)
            payload.Append( (char)(r.Next(65,90)));

        //create a unique file
        var fname = "";
        do
        {
            //fname = @"c:\source\csharp\asyncdemo\" + r.Next (1, 99999999).ToString () + ".txt";
            fname =  r.Next (1, 99999999).ToString () + ".txt";
        } while(File.Exists(fname));            

        //write the string to disk in async manner
        using(FileStream fs = new FileStream(fname,FileMode.CreateNew,FileAccess.Write, FileShare.None,
            bufferSize: 4096, useAsync: true))
        {
            var bytes=(new System.Text.ASCIIEncoding ()).GetBytes (payload.ToString());
            await fs.WriteAsync (bytes,0,bytes.Length);
            fs.Close ();
        }

        //read the string back from disk in async manner
        payload = new StringBuilder ();
        //FileStream ;
        //payload.Append(await fs.ReadToEndAsync ());
        using (var fs = new FileStream (fname, FileMode.Open, FileAccess.Read,
                   FileShare.Read, bufferSize: 4096, useAsync: true)) {
            int numRead;
            byte[] buffer = new byte[0x1000];
            while ((numRead = await fs.ReadAsync (buffer, 0, buffer.Length)) != 0) {
                payload.Append (Encoding.Unicode.GetString (buffer, 0, numRead));
            }
        }


        //fs.Close ();
        //File.Delete (fname); //remove the file
    }

    public void ProcessRequest (HttpContext context)
    {
        Task task = new Task(processAsync);
        task.Start ();
        task.Wait ();

        //write the string back on the response stream
        context.Response.ContentType = "text/plain";
        context.Response.Write (payload.ToString());
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM