这是编写异步方法的正确方法吗?

Com*_*ity 4 c# asynchronous async-await c#-5.0

我目前正在尝试编写异步代码,我觉得我的代码根本不太正确.

我有以下方法:

public void Commit()
{
    _context.SaveChangesToDatabase();
}
Run Code Online (Sandbox Code Playgroud)

不要在这里判断代码,因为这只是样本.此外,不要说如果我使用实体框架,它们已经与Async方法打包在一起.我只是想了解这里的异步概念.

假设该方法SaveChangesToDatabase确实需要几秒钟才能完成.现在,我不想等待它,所以我创建了一个异步方法:

public async Task CommitAsync()
{
    await Task.Run(() => Commit());
}
Run Code Online (Sandbox Code Playgroud)

这是否意味着如果我有一个方法:

public void Method()
{
    // Operation One:

    CommitAsync();

    // Operation Two.
}
Run Code Online (Sandbox Code Playgroud)

这是否意味着我的操作二代码将在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;
}
Run Code Online (Sandbox Code Playgroud)

这确实意味着我需要将async修饰符放在调用此代码的方法上,以便我可以等待这意味着继续当前执行并在此方法完成时返回.

Jer*_*ert 8

火但不要忘记

CommitAsync()返回a Task,但完全Method忽略了返回值CommitAsync- 所以是的,代码不会等待,而只是继续之后的内容.这很糟糕,因为如果Commit()抛出异常,你将永远不会看到它.理想情况下,每个任务都应该由某人在某个地方等待,因此您至少可以看到它是否失败.

假设您没有异步替代SaveChangesToDatabase,但无论如何您都希望在异步上下文中使用它.您可以使用Task.Run创建"假异步"方法,但不建议这样做(见下文):

public Task CommitAsync() {
    return Task.Run(() => Commit());
}
Run Code Online (Sandbox Code Playgroud)

然后,假设Method正在做一些有趣的异步(下面的代码没有做,因为它是那里唯一的异步操作):

public async Task MethodAsync() {
    // Operation One:

    await CommitAsync();

    // Operation Two.
}
Run Code Online (Sandbox Code Playgroud)

假设您不想等待,但如果任务失败,您确实想要执行某些操作,则可以使用单独的方法:

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
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

取回结果

我们假设.Commit()确实返回了一些东西(比如受影响的记录数); 一个类似的"假异步"包装器(再次,不推荐 - 见下文)看起来像这样:

public Task<int> CommitAsync() {
    return Task.Run(() => Commit());
}
Run Code Online (Sandbox Code Playgroud)

如果您想要此结果,可以立即等待任务:

public async Task MethodAsync() {
    // Operation One:

    int recordsAffected = await CommitAsync();

    // Operation Two.
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您不立即需要,请在以下情况下使用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;
}
Run Code Online (Sandbox Code Playgroud)

异步:不要伪造它

一般来说,你不想编写包装器,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.
}
Run Code Online (Sandbox Code Playgroud)