C# 中 async/await 調用傳統 Begin/End 異步方法


最近在改進園子的圖片上傳程序,希望實現用戶上傳圖片時同時將圖片文件保存在三個地方:1)服務器本地硬盤;2)又拍雲;3)阿里雲OSS。並且在保存時使用異步操作。

對於異步保存到本地硬盤,只需用 Steam.CopyToAsync() 將上傳文件流異步復制到 FileStream 即可。

對於異步保存至又拍雲,只要借助 WebRequest.GetRequestStreamAsync() + Steam.CopyToAsync() 就可以實現。

而阿里雲OSS提供了 .NET SDK,使用起來很方便,但是之前並沒有提供異步接口,成為異步化的一個障礙。

今天在 OSS .NET SDK 的更新日志中驚喜地發現:“添加異步化接口(支持Put/Get/List/Copy/PartCopy等異步操作)”。於是立馬下載下來,可是一使用驚喜瞬間化為烏有 —— 新版 SDK 只提供了傳統的 Begin/End 異步接口,卻沒有提供 async 異步接口。

public IAsyncResult BeginPutObject(string bucketName, string key, Stream content, AsyncCallback callback, object state);
public PutObjectResult EndPutObject(IAsyncResult asyncResult);

懷着失落的心情,望着孤零零的沒有 await 陪伴的 async,心里有說不出的滋味。。。

async Task<bool> IBucket.PutFileAsync(string filePath, Stream uploadStream)
{
    filePath = filePath.Substring(1);
    uploadStream.Position = 0;
    _client.PutObject(_bucketName, filePath, uploadStream);
    return true;
}

難道這次只能實現半吊子的異步化嗎?好不容易等來 OSS .NET SDK 支持異步化,難道只是空歡喜一場嗎?真有些不甘心啊!

這時,心中突然閃過一個念頭:有沒有可能直接用 async/await 調用 Begin/End 異步方法?也許微軟早就為我們准備好了餡餅?

於是,在網上搜尋了一番,發現了一線希望 —— 用 Task.Factory.FromAsync() 是可能實現的。

可是,一堆 FromAsync 方法看着就讓人暈,只能一點點去試。

開始用的是 Task.Factory.FromAsync<PutObjectResult> ,但參數總對不上,比如:

await Task.Factory.FromAsync<PutObjectResult>(
                _client.BeginPutObject,
                _client.EndPutObject,
                _bucketName, filePath, uploadStream,
                null);

編譯出錯:

No overload for method 'FromAsync' takes 6 arguments

后來改為下面這樣,總算編譯通過了:

await Task.Factory.FromAsync<PutObjectResult>(
                _client.BeginPutObject(_bucketName, filePath, uploadStream, null, null),
                x => { return _client.EndPutObject(x); });

而且運行程序,圖片都能成功上傳到阿里雲OSS中,但總是報這樣的錯誤:

failed: System.ArgumentException : retryableAsyncResult should not be null
	at Aliyun.OpenServices.OpenStorageService.Utilities.OssUtils.EndOperationHelper(IServiceClient serviceClient, IAsyncResult asyncResult)

這個錯誤說明 callback 調用沒成功。

在這個地方折騰了很長時間,后來瞎貓碰着死耗子,把 Task.Factory.FromAsync<PutObjectResult> 改為 Task<PutObjectResult>.Factory.FromAsync 問題就解決了。代碼如下:

async Task<bool> IBucket.PutFileAsync(string filePath, Stream uploadStream)
{
    filePath = filePath.Substring(1);
    uploadStream.Position = 0;
    var result = await Task<PutObjectResult>.Factory.FromAsync(
                    _client.BeginPutObject,
                    _client.EndPutObject,
                    _bucketName, filePath, uploadStream,
                    null);
    Console.WriteLine(result.ETag);
    return true;
}

【2017-7-25更新】

感謝評論中 @唐詩 的建議!TaskCompletionSource 的確是更好的解決方法,最新的實現代碼如下:

Task<bool> IBucket.PutFileAsync(string filePath, Stream uploadStream, string contentType)
{
    filePath = filePath.Substring(1);
    uploadStream.Position = 0;

    var tcs = new TaskCompletionSource<bool>();            

    _client.BeginPutObject(_bucketName,
        filePath,
        uploadStream,
        new ObjectMetadata { ContentType = contentType },
        asyncResult =>
        {
            _client.EndPutObject(asyncResult);
            tcs.SetResult(true);
        },
        null);

    return tcs.Task;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM