【问题标题】:How to implment my async method in the right way? [closed]如何以正确的方式实现我的异步方法? [关闭]
【发布时间】:2012-12-12 08:42:01
【问题描述】:

我想知道如何实现我自己的异步方法的最佳方式。我读过this article,它说将同步方法包装到任务中并将其用作异步方法不是一个好主意,因为这种方式使用其他线程并使用更多资源,这是我们想要避免的使用异步。

在文章中使用TaskCompletionSource,但我不知道具体如何使用。 TaskCompletionSource 的目的是什么?

【问题讨论】:

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


    【解决方案1】:

    编写async 方法的最佳资源是Task-based Asynchronous Pattern (TAP) document by Stephen Toub。该文档中的大部分信息现在是part of the official MSDN documentation

    如果您想要一个更短(但仍然很全面)的教程,我推荐我自己的 async/await intro blog post,还有其他几个教程。一旦您对async 有所了解,下一个好资源就是official async/await FAQ

    Channel9 上还有大量精彩的 async 视频。当它还是 CTP 时,他们中的许多人(大多数?)都在讨论 async,但在投入生产时几乎没有进行设计更改。

    为了回答您的具体问题,TaskCompletionSource 用于围绕现有异步操作(例如,I/O 操作、计时器或事件)创建 Task 包装器。你不能在TaskCompletionSource创建的Task中执行代码;您应该使用Task.Run 来执行Task 中的代码。

    【讨论】:

    • 但是如果我使用 task.Run 我会创建一个新线程,如果我没记错的话,这会创建一个新线程,这是我们想要避免的,因为使用更多资源并且可伸缩性更差。是否可以在不创建新线程的情况下执行任务中的代码?
    • 代码还要在哪里运行?
    • 是的。然后,如果我使用像killercam这样的代码,那是我正在做的,与将同步方法包装在任务中以创建异步方法相同。如果这是真的,在我的帖子中,作者说这不是一个好习惯。
    【解决方案2】:

    在另一个 async 方法中调用一个方法(使用 .NET 5.0 async 关键字)对我来说很好。我不确定我是否完全理解你的问题,但举一个简单的例子。要显示起始 int 和计数之间的质数,我们可以编写

    async void DisplayPrimes()
    {
        int result = await GetPrimesCountAsync(2, 10000);
        Console.WriteLine(result);
    }
    

    在哪里

    Task<int> GetPrimesCountAsync(int start, int count)
    {
        return Task.Run(() => 
            ParrallelEnumerable.Range(start, count).COunt(n => 
                Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
    }
    

    这里的async 修饰符告诉编译器如果在该方法中出现任何歧义,则将其视为关键字而不是标识符。这确保了在 C# 5.0 之前编写的可能使用 await 作为标识符的代码仍然可以无错误地编译。 async 修饰符只能应用于返回 void 或 Task/Task&lt;TResult&gt; 的方法和 lambda 表达式。

    遇到await 表达式时,执行(通常)返回给调用者——就像在迭代器中使用yeild return。但是在返回之前,运行时会为等待的任务附加一个延续,确保当任务完成时,执行会跳回方法并从中断的地方继续。我想您可以编写上面的方法来显示编译器将其转换为的内容

    void DisplayPrimesCount()
    {
        var awaiter = GetPrimesCountAsync(2, 100000).GetAwaiter();
        awaiter.OnCompleted(() =>
        {
            int result = awaiter.GetResult();
            Console.WriteLine(result);
        });
    }
    

    我希望这会有所帮助。


    编辑。所以为了避免任务阻塞(很难说出你的情况是什么,因为你没有发布任何代码),你需要把你想要创建的async进程“提升一个级别”

    private async Task<bool> TestAsync()
    {
        return await Task.Run(() => 
        {
            // Do stuff non-blocking.            
            return true;
        }).ConfigureAwait(continueOnCapturedContext:false);
    }
    

    TestAsync 这里将能够通过在线程池线程而不是 UI 上下文上执行其“return”语句来完成。这将立即返回给调用者,但请注意,如果您继续使用 Task.Result,那么任何异常都会被包裹在 AggregateException 中,您必须相应地处理。

    可以在on MSDN here 找到一个很好的解释。我建议在进入 async/await 之前学习 C# 4.0 任务,因为它会让你更好地理解并发性。

    【讨论】:

    • 我对这篇文章的理解是正确的,问题是如果我使用 Tas.Run,​​我会创建一个新线程,这会占用更多资源,所以目标不是为异步方法。问题是,目前,我不知道如何在没有 task.Run 的情况下实现异步方法并且不阻塞 UI。文章使用了TaskCompletionSource,它不会创建新线程,但我不知道如何正确使用它。
    • 查看编辑以获得更多说明。
    • 听起来你误解了运行“异步”的概念,因为它本质上总是涉及第二个线程。如果您对创建/删除其他线程感到不自在,您可以创建一个额外的线程来等待处理事情(没有繁忙的循环!),实质上是重用其资源。缺点是每次运行只能处理一个异步请求,但它会起作用。
    • @Mario:“基本上总是涉及第二个线程。”我不同意这种说法。只有异步代码需要另一个线程;所有其他异步操作(I/O、定时器、事件)都没有。
    • 到目前为止,我一直按照您在 las 示例中所说的方式进行操作。在你放“做非阻塞的东西”的地方,我使用了一种同步方法,因为它可能是复杂的代码并且更具可读性。但是这种方式,就是在一个任务中包装一个同步方法来创建我的异步方法。这是文章作者试图避免的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-13
    • 2015-05-31
    相关资源
    最近更新 更多