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

时间:2015-09-10 21:00:32

标签: c# .net 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);
    }

但是,我想要编写另一种方法,非常相似,但它会调用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);
    }

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

思想?

2 个答案:

答案 0 :(得分:18)

  

但是,我不喜欢有两种方法,几乎​​是重复的代码。我怎么能避免这个?

我的MSDN article on brownfield async development中列出了一些方法。

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

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

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

2)在异步版本上应用 hack 阻止。

public void Encrypt(IFile file)
{
  EncryptAsync(file).GetAwaiter().GetResult();
}

请注意(正如我在博客中所述),to avoid deadlocks you'll need to use ConfigureAwait(false) everywhere in your asynchronous version

这个黑客的缺点:

  • ConfigureAwait(false)版本中的每个await及其调用的每个方法都必须使用async。忘掉一个,你就有了死锁的可能性。请注意,某些库不会在所有平台上使用ConfigureAwait(false)(特别是移动平台上的HttpClient)。

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

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

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

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

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);
}

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

我的文章中介绍了其他一些黑客攻击(单线程线程池上下文和嵌套的消息循环),但我通常不推荐它们。

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

答案 1 :(得分:-1)

这样的事情:

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);
}