这是编写异步方法的正确方法吗
本文关键字:方法 异步方法 | 更新日期: 2023-09-27 18:27:28
我目前正在尝试编写异步代码,我觉得我的代码根本不太正确。
我有以下方法:
public void Commit()
{
_context.SaveChangesToDatabase();
}
不要在这里判断代码,因为这只是示例。此外,不要说如果我使用的是实体框架,那么它们已经与异步方法打包在一起了。我只是想理解这里的异步概念。
假设SaveChangesToDatabase
的方法需要几秒钟才能完成。现在,我不想等待它,所以我创建了一个异步方法:
public async Task CommitAsync()
{
await Task.Run(() => Commit());
}
这是否意味着,如果我有一个方法:
public void Method()
{
// Operation One:
CommitAsync();
// Operation Two.
}
这是否意味着我在操作二上的代码将在CommitAsync()
完成之前执行?
如果没有,请指引我正确的方向。
更新
根据这里的注释,我忽略了异步方法的结果,这个实现更好吗?
public Task<TaskResult> CommitAsync()
{
var task = new Task<TaskResult>(() =>
{
try { Commit(); }
catch (Exception ex)
{
return new TaskResult
{
Result = TaskExceutionResult.Failed,
Message = ex.Message
};
}
return new TaskResult { Result = TaskExceutionResult.Succeeded };
});
task.Start();
return task;
}
这意味着我需要在调用此代码的方法上加上async
修饰符,这样我就可以等待它,这意味着继续当前执行,并在该方法完成后返回。
开火,但别忘了
CommitAsync()
返回一个Task
,但Method
完全忽略了CommitAsync
的返回值——所以是的,代码不会等待,而是继续执行之后的操作。这很糟糕,因为如果Commit()
抛出异常,你永远不会看到它。理想情况下,每个任务都应该有人在某个地方等待,这样你至少可以看到它是否失败。
假设您没有SaveChangesToDatabase
的异步替代方案,但无论如何您都希望在异步上下文中使用它。可以使用Task.Run
创建"伪异步"方法,但不建议这样做(见下文):
public Task CommitAsync() {
return Task.Run(() => Commit());
}
然后,假设Method
正在用async做一些有趣的事情(下面的代码做而不是,因为它是其中唯一的异步操作):
public async Task MethodAsync() {
// Operation One:
await CommitAsync();
// Operation Two.
}
假设你不想等待,但如果任务失败,你确实想做点什么,你可以使用一个单独的方法:
public void Method() {
// Operation One:
var _ = TryCommitAsync();
// Operation Two.
}
private async Task TryCommitAsync()
{
try
{
await CommitAsync();
}
catch (Exception ex)
{
Console.WriteLine(
"Committing failed in the background: {0}",
ex.Message
);
}
}
正在获取结果
假设.Commit()
确实返回了一些内容(比如受影响的记录数);类似的"伪异步"包装器(同样,不推荐使用-见下文)看起来是这样的:
public Task<int> CommitAsync() {
return Task.Run(() => Commit());
}
如果你想要这个结果,你可以立即等待任务:
public async Task MethodAsync() {
// Operation One:
int recordsAffected = await CommitAsync();
// Operation Two.
}
或者,如果您不需要立即使用,请在需要时使用await
:
public async Task MethodAsync() {
// Operation One:
Task<int> commit = CommitAsync();
// Operation Two.
// At this point I'd really like to know how many records were committed.
int recordsAffected = await commit;
}
异步:不要伪造
一般来说,您不想编写像CommitAsync()
这样的包装器,因为它们会误导调用方,使其认为代码将是异步的,而事实并非如此,这除了不阻塞之外几乎没有什么好处(这在UI代码中仍然很有用,但不如真正的异步代码好,因为它不需要对所有事情都使用工作线程)。换言之,您应该在方法的调用中使用Task.Run
,而不是将其用作方法的实现。
因此,作为一种习惯,不要为您拥有的每个同步方法编写像CommitAsync
这样的包装器——相反,您希望制作一个真正的CommitAsync
,它使用底层库/框架(SqlCommand.ExecuteReaderAsync()
等)的异步支持
如果您别无选择,必须使用Task.Run
,那么适当的用法看起来更像:
// This method is in the UI layer.
public async Task MethodAsync() {
// Operation One:
// Commit() is a method in the DA layer.
await Task.Run(() => Commit());
// Operation Two.
}
此处为
http://channel9.msdn.com/events/TechEd/NorthAmerica/2013/DEV-B318#fbid=
很好地解释了如何使用异步,以及为什么应该避免"异步过度同步",这就是您现在使用所做的
public Task CommitAsync() {
return Task.Run(() => Commit());
}
在某些情况下,你可以从中受益,但如果你要将其作为库的一部分提供,那么做这件事不是一个好主意。如果这个代码是ONLY并且ONLY将由你的应用程序使用,并且你确定你正在做什么,并且在你的异步方法中没有调用异步方法的方法,那么就这样做吧