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

时间:2014-10-22 08:22:51

标签: c# asynchronous async-await c#-5.0

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

我有以下方法:

public void Commit()
{
    _context.SaveChangesToDatabase();
}

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

让我们说方法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修饰符,以便我可以等待这意味着继续当前执行并在此方法完成时返回。

2 个答案:

答案 0 :(得分:8)

火,但不要忘记

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

让我们说你没有SaveChangesToDatabase的异步替代品,但无论如何你想在异步上下文中使用它。您可以使用Task.Run来创建&#34;假异步&#34;方法,但不建议这样做(见下文):

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()确实返回了某些内容(比如受影响的记录数);类似的&#34;假异步&#34;包装(再次,不推荐 - 见下文)看起来像这样:

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()之类的包装器,因为它们mislead callers into thinking code will be asynchronous when it isn't really,除了不阻塞之外几乎没有什么好处(在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.
}

答案 1 :(得分:0)

这里

http://channel9.msdn.com/events/TechEd/NorthAmerica/2013/DEV-B318#fbid=

是关于如何使用异步的一个很好的解释,以及为什么你应该避免&#34; async over sync&#34;,这就是你现在正在做的事情

public Task CommitAsync() {
    return Task.Run(() => Commit());
}

在某些情况下,您可以从中受益,但如果您要将此作为库的一部分提供,则不是一个好主意。 如果这个代码只是由您的应用程序使用,并且您确定自己在做什么并且没有在异步方法中调用异步方法,那就去做吧