使用Async/Await时,流正在关闭

3

我有一个小型的服务来上传blob到Azure Storage。我试图从WebApi的异步操作中使用它,但我的说流已关闭。

我对async/await不熟悉,有什么好的资源可以帮助我更好地理解它吗?

WebApi控制器

public class ImageController : ApiController
{
    private IFileStorageService fileStorageService;

    public ImageController(IFileStorageService fileStorageService)
    {
        this.fileStorageService = fileStorageService;
    }

    public async Task<IHttpActionResult> Post()
    {
        if (!Request.Content.IsMimeMultipartContent())
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType));
        }

        await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider()).ContinueWith((task) =>
        {

            foreach (var item in task.Result.Contents)
            {
                using (var fileStream = item.ReadAsStreamAsync().Result)
                {
                    fileStorageService.Save(@"large/Sam.jpg", fileStream);
                }

                item.Dispose();
            }

        });

        return Ok();
    }
}

AzureFileStorageService(Azure文件存储服务)
public class AzureFileStorageService : IFileStorageService
{
    public async void Save(string path, Stream source)
    {
        await CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"])
            .CreateCloudBlobClient()
            .GetContainerReference("images")
            .GetBlockBlobReference(path)
            .UploadFromStreamAsync(source); // source throws a stream is disposed exception
    }
}

事实上,这可能就是问题所在。所有都已经使用了 asyncawait 进行设置;using 块可能会在你获取结果之前过早地关闭流。 - Robert Harvey
@RobertHarvey - 所以在这种情况下,item.Dispose()Result 上的 Dispose() 都会被调用,而 Result 是一个 Stream - Sam
你的 Save() 方法存在问题:你没有返回一个 Task,因此调用该方法的方法无法等待它完成。因此,在你调用它之后,你离开了 using 块并且流被释放,很可能在保存完成之前就已经被释放了。 - dlev
@Sam:是的,你说得对,这是两个不同的Dispose。但问题仍然存在,你提前关闭/丢弃了流。 - Robert Harvey
1
item.Dispose() 是可以的:using 语句正在处理流,而不是项目。 - dlev
显示剩余4条评论
2个回答

8
你的Save()方法有问题:你没有返回Task,因此调用该方法的方法无法等待它完成。如果你只想要执行并忘记它,那么这样做就没问题了,但是你不能这样做,因为你传递的流会在Save()方法返回后被释放(感谢using语句)。
相反,你需要返回一个Task并在调用方法中使用await,或者你需要不将文件流放在using块中,而是让Save()方法在完成时处理它的释放。
你可以按以下方式重新编写代码:
(调用方法的片段):
    var result = await Request.Content.ReadAsMultipartAsync(new MultipartMemoryStreamProvider());
    foreach (var item in result.Contents)
    {
        using (var fileStream = await item.ReadAsStreamAsync())
        {
            await fileStorageService.Save(@"large/Sam.jpg", fileStream);
        }

        item.Dispose();
    }

而保存方法:

public async Task Save(string path, Stream source)
{
    await CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"])
        .CreateCloudBlobClient()
        .GetContainerReference("images")
        .GetBlockBlobReference(path)
        .UploadFromStreamAsync(source);
}

此外,ContinueWith 可以被 await 替代。 - Stephen Cleary
非常感谢!你有没有推荐一些简单明了解释await/async的网站? - Sam
@StephenCleary - await 可以在哪里被替换? - Sam
@Sam MSDN文档是一个不错的起点,Stephen Toub和Eric Lippert各自的博客文章也是很好的、易于理解的资源。而且,坦率地说,Stephen Cleary也有一系列不错的文章 :) - dlev
1
@Sam,你的代码目前在等待结果之前使用了 ContinueWith。但是 await 本质上是一种绕过完全需要 ContinueWith 的方法:只需等待原始任务,然后整个方法自动成为延续。因此,你可以直接删除 ContinueWith - dlev
显示剩余3条评论

0

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