Refactoring a library to be async, how can I avoid repeating myself?
我有这样的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void Encrypt(IFile file) { if (file == null) throw new ArgumentNullException(nameof(file)); string tempFilename = GetFilename(file); using (var stream = new FileStream(tempFilename, FileMode.OpenOrCreate)) { this.EncryptToStream(file, stream); file.Write(stream); } File.Delete(tempFilename); } |
但是,我想编写另一个方法,非常相似,但是它调用WriteAsync,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public async Task EncryptAsync(IFile file) { if (file == null) throw new ArgumentNullException(nameof(file)); string tempFilename = GetFilename(file); using (var stream = new FileStream(tempFilename, FileMode.OpenOrCreate)) { this.EncryptToStream(file, stream); await file.WriteAsync(stream); } File.Delete(tempFilename); } |
但是我不喜欢使用两种方法编写几乎重复的代码。 如何避免这种情况? 正确的方法感觉就像我应该使用"动作/委托",但是签名不同。
有什么想法吗?
However I don't like having two methods with practically duplicate code. How can I avoid this?
正如我在MSDN上有关Brownfield异步开发的文章中概述的那样,有几种方法。
1)使自然异步API仅异步。
这是最激烈的解决方案(就向后兼容性而言),但是从技术角度来看,您可能会认为这是最正确的。使用这种方法,您可以将
虽然这是IMO的最佳方法,但您的最终用户可能不同意。 :)
2)在异步版本上应用阻止的技巧。
1 2 3 4 | public void Encrypt(IFile file) { EncryptAsync(file).GetAwaiter().GetResult(); } |
请注意(如我在博客中所述),为避免死锁,您将需要在异步版本中的所有地方使用
这种黑客的缺点:
-
实际上,您必须为
async 版本中的每个await 及其调用的每个方法使用ConfigureAwait(false) 。即使忘记一个,您也有可能陷入僵局。请注意,某些库并未在所有平台上都使用ConfigureAwait(false) (特别是在移动平台上的HttpClient )。
3)在线程池线程上应用异步版本并在其上阻塞的技巧。
1 2 3 4 | public void Encrypt(IFile file) { Task.Run(() => EncryptAsync(file)).GetAwaiter().GetResult(); } |
此方法完全避免了死锁情况,但有其自身的缺点:
- 异步代码必须能够在线程池线程上运行。
- 异步代码可以在其整个存在期间在多个线程池线程上运行。如果异步代码隐式依赖于单线程上下文的同步,或者如果它使用线程本地状态,则这种方法在不进行一些重写的??情况下将无法工作。
4)传递标志参数。
如果您不愿意采用方法(1),这就是我最喜欢的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private async Task EncryptAsync(IFile file, bool sync) { if (file == null) throw new ArgumentNullException(nameof(file)); string tempFilename = GetFilename(file); using (var stream = new FileStream(tempFilename, FileMode.OpenOrCreate)) { this.EncryptToStream(file, stream); if (sync) file.Write(stream); else await file.WriteAsync(stream); } File.Delete(tempFilename); } public void Encrypt(IFile file) { EncryptAsync(file, sync: true).GetAwaiter().GetResult(); } public Task EncryptAsync(IFile file) { return EncryptAsync(file, sync: false); } |
布尔标志肯定是丑陋的-也是正确的OOP设计的红色标志-但它隐藏在
我的文章中还介绍了其他一些技巧(单线程线程池上下文和嵌套的消息循环),但是我通常不推荐它们。
附带说明一下,如果您的代码确实在使用
像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public async Task EncryptAsync(IFile file, bool AAsync) { if (file == null) throw new ArgumentNullException(nameof(file)); string tempFilename = GetFilename(file); using (var stream = new FileStream(tempFilename, FileMode.OpenOrCreate)) { this.EncryptToStream(file, stream); if (AAsync) await file.WriteAsync(stream); else file.Write(stream); } File.Delete(tempFilename); } |