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

时间:2014-07-01 12:35:20

标签: c# asp.net asynchronous .net-4.0 task-parallel-library

我有一个http处理程序,应该写入输出一些文本。文本内容是异步检索的,所以我想在ProcessRequest方法中写入响应流,如下所示:

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

但是,我得到一个带有堆栈跟踪的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()

如果我在task.Wait()之后不使用ContinueWith并写入响应 - 没有错误,但显然它不是解决方案。

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

如何消除此错误? (使用.net 4.0)

3 个答案:

答案 0 :(得分:4)

您需要实施IHttpAsyncHandler。检查"Walkthrough: Creating an Asynchronous HTTP Handler"

最重要的是,您可以使用async/await复制流(请注意下面的CopyAsync)。为了能够使用async/await并将.NET 4.0与VS2012 +作为目标,请将Microsoft.Bcl.Async包添加到您的项目中。

这样,没有线程被不必要阻止。一个完整的例子(未经测试):

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
}

答案 1 :(得分:0)

此问题可能基于“上下文切换”,该任务在其自己的线程中执行时发生。

当前HttpContext仅在请求线程中可用。但是你可以制作一个“本地参考”来访问它(但这不能完全解决你的问题 - 见下文):

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

现在的问题是,在执行ContinueWith时您的请求很可能已经完成,因此您的客户端流已经关闭。
在此之前,您需要将内容写入流中。

我需要完成答案的以下部分。 Http处理程序不是async - 在4.0中有能力,因为这会占用4.5 {>之前不可用的HttpTaskAsyncHandler基类。使用http处理程序时,我不知道4.0中Wait方法中ProcessRequest的方法:

我建议使用以下内容:

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

和相应的NuGet-Package for await

答案 2 :(得分:-3)

最近,我遇到了类似的任务,要编写一个ashx处理程序来异步放置响应。目的是生成一些大字符串,执行I / O并将其返回到响应流上,从而将ASP.NET与其他languages like node.js基准测试用于严重I / O绑定的应用程序我将去开发。这就是我最终做的事情(它有效):

    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());
    }