Ответ 1
Однако мне не нравится иметь два метода с практически дублирующимся кодом. Как я могу избежать этого?
Существует несколько подходов, описанных в моей статье MSDN, посвященной разработке асинхронного браузера.
1) Сделайте асинхронные API-интерфейсы только асинхронными.
Это самое решительное решение (с точки зрения обратной совместимости), но с технической точки зрения вы можете утверждать, что это наиболее правильно. При таком подходе вы замените Encrypt
на EncryptAsync
.
Хотя это лучший подход к ИМО, конечные пользователи могут не согласиться.:)
2) Примените взломать блокировки в асинхронной версии.
public void Encrypt(IFile file)
{
EncryptAsync(file).GetAwaiter().GetResult();
}
Обратите внимание, что (как я описываю в своем блоге) чтобы избежать взаимоблокировок, вам нужно использовать ConfigureAwait(false)
всюду в вашей асинхронной версии.
Недостатки этого взлома:
- Вам буквально приходится использовать
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);
}
Булевский флаг, безусловно, уродливый - и красный флаг для правильного дизайна ООП, но он скрыт в методе private
и не так опасен, как другие хаки.
В моей статье рассматривается несколько других хаков (однопоточный поток пула потоков и вложенные петли сообщений), но я вообще не рекомендую их.
На стороне примечания, если ваш код действительно использует FileStream
, вам нужно явно открыть его для асинхронного доступа для получения истинных асинхронных операций. То есть вы должны вызвать конструктор либо передать true
для параметра isAsync
, либо установить флаг FileOptions.Asynchronous
в значение для параметра options
.