简体   繁体   中英

Write in ASP.NET response output stream from task continuation

I have a http handler which should write to output some text. Text content is retrieved asynchronously, so I want to write to response stream in the ProcessRequest method like this:

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

However, I get a NullReferenceException with the stack trace

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.

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)

You need to implement IHttpAsyncHandler . Check "Walkthrough: Creating an Asynchronous HTTP Handler" .

On top of that, you can use async/await to copy the stream (note CopyAsync below). To be able to use async/await and target .NET 4.0 with VS2012+, add Microsoft.Bcl.Async package to your project.

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. 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.
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:

I recommend using something like the following:

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

and the appropriate NuGet-Package for await .

Quite recently, I had come across the similar task to write an ashx handler to put response asynchronously. 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. 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());
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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