Я использую этот вопрос в качестве основы для моего вопроса.
TL; DR : если вы не должны переносить синхронный код в асинхронную оболочку, как вы справляетесь с долговременными методами блокировки потоков, которые реализуют интерфейсный метод, который ожидает асинхронный реализация?
Допустим, у меня есть приложение, которое работает непрерывно для обработки рабочей очереди. Это серверное приложение (работающее в основном без присмотра), но в нем есть пользовательский интерфейс для более детального контроля поведения приложения в соответствии с требованиями бизнес-процессов: запуск, остановка, настройка параметров во время выполнения, получение прогресса и т. д.
Существует слой бизнес-логики, в который сервисы внедряются как зависимости.
BLL определяет набор интерфейсов для этих служб.
Я хочу, чтобы клиент реагировал: позволить клиенту пользовательского интерфейса взаимодействовать с запущенным процессом, а также хочу, чтобы потоки использовались эффективно, потому что процесс должен быть масштабируемым: может быть любое количество асинхронных операций с базой данных или диском в зависимости от на работу в очереди. Таким образом, я использую async / await "полностью" .
Для этого у меня есть методы в сервисных интерфейсах, которые, очевидно, предназначены для поощрения async / await и поддержки отмены, потому что они принимают CancellationToken
, имеют имя "Async" и возвращают Task
s.
У меня есть служба хранилища данных, которая выполняет операции CRUD для сохранения сущностей моего домена. Допустим, в настоящее время я использую API для этого, который изначально не поддерживает async . В будущем я могу заменить его на тот, который работает, но в настоящее время служба хранилища данных выполняет большинство своих операций синхронно, многие из них являются длительными операциями (поскольку API блокирует ввод-вывод базы данных).
Теперь я понимаю, что методы, возвращающие Task
s, могут работать синхронно. Методы в моем классе обслуживания, которые реализуют интерфейсы в моем BLL, будут работать синхронно, как я объяснил, но потребитель (мой BLL, клиент и т. Д.) Будет предполагать , что они либо 1: работают асинхронно, либо 2: работают синхронно в течение очень короткого времени. То, что методы не должны делать, это заключать синхронный код в асинхронный вызов в Task.Run
.
Я знаю, что мог бы определить как интерфейс синхронизации, так и асинхронные методы в интерфейсе.
В этом случае я не хочу этого делать из-за асинхронной семантики «полностью», которую я пытаюсь использовать, и потому, что я не пишу API для использования клиентом; как уже упоминалось выше, я не хочу позже менять свой код BLL с использования версии синхронизации на использование асинхронной версии.
Вот интерфейс службы данных:
public interface IDataRepository
{
Task<IReadOnlyCollection<Widget>>
GetAllWidgetsAsync(CancellationToken cancellationToken);
}
И это реализация:
public sealed class DataRepository : IDataRepository
{
public Task<IReadOnlyCollection<Widget>> GetAllWidgetsAsync(
CancellationToken cancellationToken)
{
/******* The idea is that this will
/******* all be replaced hopefully soon by an ORM tool. */
var ret = new List<Widget>();
// use synchronous API to load records from DB
var ds = Api.GetSqlServerDataSet(
"SELECT ID, Name, Description FROM Widgets", DataResources.ConnectionString);
foreach (DataRow row in ds.Tables[0].Rows)
{
cancellationToken.ThrowIfCancellationRequested();
// build a widget for the row, add to return.
}
// simulate long-running CPU-bound operation.
DateTime start = DateTime.Now;
while (DateTime.Now.Subtract(start).TotalSeconds < 10) { }
return Task.FromResult((IReadOnlyCollection<Widget>) ret.AsReadOnly());
}
}
BLL:
public sealed class WorkRunner
{
private readonly IDataRepository _dataRepository;
public WorkRunner(IDataRepository dataRepository) => _dataRepository = dataRepository;
public async Task RunAsync(CancellationToken cancellationToken)
{
var allWidgets = await _dataRepository
.GetAllWidgetsAsync(cancellationToken).ConfigureAwait(false);
// I'm using Task.Run here because I want this on
// another thread even if the above runs synchronously.
await Task.Run(async () =>
{
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var widget in allWidgets) { /* do something */ }
await Task.Delay(2000, cancellationToken); // wait some arbitrary time.
}
}).ConfigureAwait(false);
}
}
Презентация и логика представления:
private async void HandleStartStopButtonClick(object sender, EventArgs e)
{
if (!_isRunning)
{
await DoStart();
}
else
{
DoStop();
}
}
private async Task DoStart()
{
_isRunning = true;
var runner = new WorkRunner(_dependencyContainer.Resolve<IDataRepository>());
_cancellationTokenSource = new CancellationTokenSource();
try
{
_startStopButton.Text = "Stop";
_resultsTextBox.Clear();
await runner.RunAsync(_cancellationTokenSource.Token);
// set results info in UI (invoking on UI thread).
}
catch (OperationCanceledException)
{
_resultsTextBox.Text = "Canceled early.";
}
catch (Exception ex)
{
_resultsTextBox.Text = ex.ToString();
}
finally
{
_startStopButton.Text = "Start";
}
}
private void DoStop()
{
_cancellationTokenSource.Cancel();
_isRunning = false;
}
Таким образом, вопрос заключается в следующем: как вы справляетесь с долговременными блокирующими методами, которые реализуют интерфейсный метод, который ожидает асинхронную реализацию? Является ли это примером, когда предпочтительнее нарушить правило «нет асинхронной оболочки для кода синхронизации»?