在Task中使用using语句

6
在ASP.NET Web API控制器中,我想返回一张图片。为了流式传输图像,我需要一个MemoryStream。通常我会将它包装在一个using语句中,以确保它在使用后被正确处理。但是,由于这是异步执行的Task,因此这种方法不起作用:
public class ImagesController : ApiController
{
    private HttpContent GetPngBitmap(Stream stream)
    {
        var pngBitmapEncoder = new PngBitmapEncoder();
        pngBitmapEncoder.Save(stream);
        stream.Seek(0, SeekOrigin.Begin);
        return new StreamContent(stream);
    }

    // GET api/images
    public Task<HttpResponseMessage> Get(string id, string path)
    {            
        //do stuff           
        return Task.Factory.StartNew(() =>
        {
            var stream = new MemoryStream(); //as it's asynchronous we can't use a using statement here!
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = GetPngBitmap(stream)
                };
                response.Content.Headers.ContentType =
                    new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
                return response;
            }
            //how can I dispose stream???
        });
    }
}

2
GetPngBitmap 的类型在这种情况下很重要。当“png位图”不再引用流时,可以释放该流。 - Caramiriel
在调用 GetPnGBitmap() 方法后,是否需要在流上调用 Dispose?或者使用 try finally 块,在 finally 中调用 Dispose,或者在返回响应之前调用 Dispose。我不明白问题出在哪里?为什么不能使用 using? - Shakti Prakash Singh
Web API 托管层会在响应写出后处理流的释放,因此您无需担心自己关闭流...另外,方法调用 GetPngBitmap 做了什么?它是将文件内容复制到内存流中吗?如果是,您无需再创建内存流,只需直接将文件流提供给 Content 作为 StreamContent 即可。 - Kiran
2
感谢您的代码...是的,流将被Web API的主机层关闭,因此您不需要从您的一侧显式执行任何操作...Web API的托管层将内容从您的内存流复制到网络流,然后关闭源流(您的内存流)...这通常适用于所有响应的行为。 - Kiran
@PanagiotisKanavos:VS2010和.NET 4.0 - Dunken
显示剩余7条评论
5个回答

6
  1. MemoryStream 是实现 IDisposable 接口的类之一,因为其基类实现了该接口。 MemoryStream 除托管内存外并不持有其他资源,所以销毁它实际上是不必要的。
  2. HttpResponseMessage 可以被释放。这意味着在整个响应被发送时,该对象将被释放。这样做将释放所包含的 HttpContent,在 StreamContent 的情况下,会释放其中包含的底层 Stream。因此,即使您有一个需要释放的 Stream,在这种情况下也不必担心。

-2

使用 "using",它会自动释放。

public class ImagesController : ApiController
{
    // GET api/images
    public Task<HttpResponseMessage> Get(string id, string path)
    {            
        //do stuff           
        return Task.Factory.StartNew(() =>
        {
           using( stream = new MemoryStream()) //as it's asynchronous we can't use a using statement here!
           {
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = GetPngBitmap(stream)
                };
                response.Content.Headers.ContentType =
                    new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
                return response;
            }
            //how can I dispose stream???
           }
        });
    }
}

你读了你修改后那行代码后面的注释吗?为什么这样会起作用? - Hans Kesting
因为当您使用“using”时,.net会在“}”后调用.Dispose()对象。如果它存在于之前(返回/异常),那么它仍然会发生。您应该阅读更多关于“using”块并了解其功能。谷歌一下。 - user2953680
但是OP表示在这种情况下不能使用“using”。你确定它可以在这里使用吗?(我不知道,因为我没有足够的异步编程经验) - Hans Kesting
也许可以尝试另一种方式来返回照片?在我的ASP.NET Web API中,我以不同的方式返回了一张图片。 - user2953680

-2

也许可以返回一个 Tuple<HttpResponseMessage, Stream> 并附加一个继续任务来释放 Stream

public Task<Tuple<HttpResponseMessage,Stream>> Get(string id, string path)
{            
    //do stuff           
    return Task.Factory.StartNew(() =>
    {
        var stream = new MemoryStream();
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = GetPngBitmap(stream)
            };
            response.Content.Headers.ContentType =
                new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
            return Tuple.Create(response, stream);
        }
    })
    .ContinueWith(t => t.Result.Item2.Close());
}

  1. 这个无法编译,你正在返回非泛型的 Task
  2. 我真的不明白返回 Tuple 有什么作用。
  3. Web API 是否知道当返回一个 Tuple 时,它应该访问其 Item1?我怀疑这一点。
- svick
@svick,1. 这个回应是一个建议,2. 除了结果之外,还应该返回一个引用到一个Stream,在从流中读取后调用Dispose()。3. 我不了解Web API,这又让我回到了1. - 我提供了一个建议,而不是一个解决方案。我不知道项目的所有方面,无法将其变成解决方案。 - RePierre

-2

你可以这样做。这将首先执行你的initialRun,然后在异步调用之后,返回in并执行continue with。

        Task<System.IO.MemoryStream> initialRun = new Task<System.IO.MemoryStream>(() =>
        {
            System.IO.MemoryStream stream = new System.IO.MemoryStream();
            // use stream
            return stream;
        });
        initialRun.ContinueWith(new Action<Task<System.IO.MemoryStream>>((stream) =>
        {
            stream.Dispose();
        }), 
        TaskScheduler.FromCurrentSynchronizationContext());
        initialRun.Start();

也许我理解有误,但最终我想返回一个HttpResponseMessage。 - Dunken
@Dunken,你可以创建无限的ContinueWith,根据自己的喜好进行调整。这个示例只是演示了如何在执行后处理某些内容。 - DevEstacion
我知道,但基本上我必须返回一个HttpResponse,然后我想处理一个流... 我不知道如何使用ContinueWith()实现这一点。 - Dunken

-2
也许这听起来很天真,但你不能在任务结束时将其处理掉吗?
var stream = new MemoryStream();
//use the stream...
stream.Dispose();

编辑:或者在MemoryStream上使用“Close()”,效果是一样的。


不,我不能。如果可能的话,我可以使用using语句。 - Dunken
抱歉,刚看到返回语句在里面。 - qwertoyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接