重构库是异步的,我怎么能避免重复自己?

Adr*_*mas 13 .net c# asynchronous dry

我有一个像这样的方法:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

但是,我想要编写另一个非常相似的方法,但它会调用WriteAsync,例如:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

但是,我不喜欢有两种方法实际上重复代码.我怎么能避免这个?正确的方法感觉我应该使用Action/Delegate,但签名是不同的....

思考?

Ste*_*ary 18

但是,我不喜欢有两种方法实际上重复代码.我怎么能避免这个?

有一些方法,如我的关于棕色异步开发的MSDN文章中所述.

1)使自然异步API仅异步.

这是最激烈的解决方案(在向后兼容性方面),但从技术角度来看,你可以说它是最正确的.使用这种方法,您将替换 EncryptEncryptAsync.

虽然这是IMO的最佳方法,但您的最终用户可能不同意.:)

2)应用hack在异步版本上阻塞.

public void Encrypt(IFile file)
{
  EncryptAsync(file).GetAwaiter().GetResult();
}
Run Code Online (Sandbox Code Playgroud)

请注意(正如我在我的博客中所描述的),为避免死锁,您需要ConfigureAwait(false)在异步版本中的任何位置使用.

这个黑客的缺点:

  • 你从字面上必须使用ConfigureAwait(false)每一个await在你的async版本,每次调用方法.忘掉一个,你就有可能陷入僵局.请注意,某些库并不ConfigureAwait(false)适用于所有平台(特别是HttpClient在移动平台上).

3)在线程池线程上应用运行异步版本的hack并阻塞.

public void Encrypt(IFile file)
{
  Task.Run(() => EncryptAsync(file)).GetAwaiter().GetResult();
}
Run Code Online (Sandbox Code Playgroud)

这种方法完全避免了死锁情况,但也有其自身的缺点:

  • 异步代码必须能够在线程池线程上运行.
  • 异步代码可以在其存在的整个多个线程池线程上运行.如果异步代码隐式地依赖于单线程上下文的同步,或者它是否使用线程本地状态,那么如果没有一些重写,这种方法将无法工作.

4)传递一个标志参数.

如果你不愿采取方法(1),这是我最喜欢的方法.

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);
}
Run Code Online (Sandbox Code Playgroud)

布尔标志肯定是丑陋的 - 并且是正确的OOP设计的红色标志 - 但它隐藏在一个private方法中,而不像其他黑客那样危险.

我的文章中介绍了其他几个hacks(单线程线程池上下文和嵌套消息循环),但我通常不推荐它们.


另外,如果您的代码确实在使用FileStream,则需要显式打开它以进行异步访问以获得真正的异步操作.也就是说,您必须调用构造函数来传递参数,或者trueisAsync参数FileOptions.Asynchronous的值中设置标志options.