我在用
this question
太长,读不下去了
:如果不应该在异步包装器中包装同步代码,那么如何处理实现需要异步实现的接口方法的长时间运行的线程阻塞方法?
有一个业务逻辑层,服务作为依赖项注入其中。
BLL为这些服务定义了一组接口。
我想让客户机保持响应性:允许UI客户机与正在运行的进程交互,我还希望线程得到有效使用,因为进程需要可伸缩性:根据队列中的工作,可以有任意数量的异步数据库或磁盘操作。因此我使用async/await
"all the way"
CancellationToken
,用“Async”命名,并返回
Task
s。
我有一个数据存储库服务,它执行CRUD操作来持久化我的域实体。假设现在,我正在使用
an API for this that doesn't natively support async
. 将来,我可能会将其替换为一个同步的操作,但目前数据存储库服务将同步执行其大部分操作,其中许多操作是长时间运行的(因为API阻塞了数据库IO)。
现在,我明白了
任务
s可以同步运行。我的服务类中实现BLL中接口的方法将如我所解释的那样同步运行,但是使用者(我的BLL、客户机等)将同步运行
它们要么是1:异步运行,要么是2:在很短的时间内同步运行。
What the methods
shouldn't
do is wrap synchronous code inside an async call to
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;
}
所以问题是:如何处理那些实现了需要异步实现的接口方法的长时间运行的阻塞方法?这是一个最好打破“同步代码没有异步包装器”规则的例子吗?