【问题标题】:Is this the correct way to write asynchronous methods?这是编写异步方法的正确方法吗?
【发布时间】:2014-12-17 15:31:45
【问题描述】:

我目前正在尝试编写异步代码,我感觉我的代码一点也不正确。

我有以下方法:

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 修饰符放在调用此代码的方法上,以便我可以等待它,这意味着继续当前执行并在此方法完成时返回。

【问题讨论】:

  • 我能理解这个问题,但您可以在几乎任何环境中轻松地自行测试,您是否尝试过自己测试?
  • 你通常会发现 async 是“传染性的”——一旦你将它应用到一个方法中,你通常会想要在你的调用方法中使用 await,然后它必须变成async他们自己,一直到顶端。
  • 抱歉,我是异步的新手,所以对于某些人来说这似乎是一个微不足道的问题,但我就是不明白。
  • 您可以自己使用play。正如@Damien_The_Unbeliever 已经提到的,要拥有你想要的await 并拥有await,你需要Method() 一直是async :)
  • 我可以推荐阅读 Stephen Taub 在 MSDN 上的博客文章:blogs.msdn.com/b/pfxteam

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


【解决方案1】:

开火但不要忘记

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() 这样的包装器,因为它们 mislead callers into thinking code will be asynchronous when it isn't really,除了不阻塞之外几乎没有什么好处(这在 UI 代码中仍然有用,但不如真正的异步代码不需要对所有事情都使用工作线程)。换句话说,您应该在方法的调用中使用Task.Run,而不是作为方法的实现

因此,作为一种习惯,不要为您拥有的每个同步方法编写像 CommitAsync 这样的包装器,而是要创建一个真正的 CommitAsync,它使用底层库/框架的异步支持 (@987654342 @,等等。)

如果你别无选择,必须使用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.
}

【讨论】:

  • @Default 有没有违反规则?
  • @Default:部分是因为我可以,部分是因为我觉得自己无法充分解决这个问题,而且我看到很多开发人员都在为此苦苦挣扎。这里有一些潜力。
  • @stack 哦,不。只是他不会从赞成票中获得任何声誉积分。就个人而言,我不明白社区 wiki 的原因,除非它被多个贡献者修改或内容可能会发生很大变化。
  • @JeroenMostert 您的代码与他的初始代码相比有什么好处?通过快速测试,他的初始代码运行异步并会更新数据库,但允许其余代码按原样执行。你的要求容器方法也是异步的,假设我不需要我的异步操作的值,为什么我需要按照你的方式这样做?
  • @JeroenMostert 感谢您的解释,现在它更有意义了。 @ 复杂性我通过简单地在一个小型控制台应用程序中执行代码并使用 WriteLines 记录进度来运行它。 Jeroen 本质上说的是代码可以正常运行,但是只运行一个方法而不需要知道它发生了什么是非常糟糕的做法,即您的数据库更新可能会失败,而您永远不知道为什么。
【解决方案2】:

这里

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

很好地解释了如何使用异步,以及为什么你应该避免“异步超过同步”,这就是你现在正在做的事情

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-02
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多