【问题标题】:How to use .net async CTP with just one thread如何仅使用一个线程使用 .net 异步 CTP
【发布时间】:2023-03-21 01:10:02
【问题描述】:

所以我最近一直在阅读大量关于网络 Async CTP 的内容,并且不断出现的一件事是这样的句子:“Asynchrony 不是关于启动新线程,而是关于多路复用工作”和“Asynchrony without多线程与 [与协作多任务处理] 的想法相同。您执行一项任务一段时间,当它产生控制权时,您在该线程上执行另一项任务一段时间。

我试图了解这样的言论是否纯粹(嗯,大部分)深奥和学术,或者是否有一些我忽略的语言结构允许我通过“等待”开始的任务神奇地在 UI 上运行线程。

在他的博客中,Eric Lippert 给出了这个例子来演示如何在没有多线程的情况下实现异步:

async void FrobAll()
{
    for(int i = 0; i < 100; ++i)
    {
        await FrobAsync(i); // somehow get a started task for doing a Frob(i) operation on this thread
    }
} 

现在,让我感兴趣的是这里的评论:“...获取一个开始任务,以便在此线程上执行 Frob(i) 操作”。

这怎么可能?这主要是理论上的评论吗?到目前为止,我能看到的唯一一个任务似乎不需要单独线程的情况(好吧,除非你检查代码,否则你无法确定)就像 Task.Delay() 之类的,可以在没有的情况下等待开始另一个线程。但我认为这是一种特殊情况,因为我没有为此编写代码。

对于希望从 GUI 线程中卸载一些他们自己的长时间运行的代码的普通用户来说,我们主要不是在谈论执行诸如 Task.Run 之类的事情来卸载我们的工作吗?它在另一个线程中?如果是这样,为什么所有这些手臂都放弃不将异步与多线程混淆?

【问题讨论】:

    标签: c# asynchronous tap ctp


    【解决方案1】:

    请看我的intro to async/await。

    在 CPU 工作的情况下,您必须使用 Task.Run 之类的东西在另一个线程中执行它。但是,有很多工作不是 CPU 工作(网络请求、文件系统请求、计时器等),并且每个都可以使用 Task 包装在线程。

    请记住,在驱动程序级别,一切都是异步的。同步 Win32 API 只是方便的包装器。

    【讨论】:

    • 不确定“驱动程序级别”在这里是否有意义。您能否引用一个参考资料,说明在 Windows 中与驱动程序的交互是异步的?
    • 我只是指出设备驱动程序是异步的。因此,例如,如果您进行异步网络通信,则没有操作系统或内核线程在 I/O 上阻塞的层,因此您可以假装它是异步的(就像 Stream 类所做的那样) -它是异步的all。
    • 参考:Windows Internals (5th ed), pg 515. 当然,您可以编写一个立即完成 IRP 的驱动程序,但您不应该编写一个在 IRP 之前阻塞内核线程的驱动程序已完成。
    • 我认为这是假设 I/O,并非每次与驱动程序的交互都是异步的。即使这样,也允许驱动程序在重叠请求上不返回 STATUS_PENDING。但是,我离题了...
    【解决方案2】:

    不是 100% 肯定,但从文章中可以看出,它允许 Windows 消息(调整大小事件、鼠标点击等)在对 FrobAsync 的调用之间交错。大致类似于:

    void FrobAll()
    {
        for(int i = 0; i < 100; ++i)
        {
            FrobAsync(i); // somehow get a started task for doing a Frob(i) operation on this thread
            System.Windows.Forms.Application.DoEvents();
        }
    }
    

    或者更准确地说:

    void FrobAll()
    {
        SynchronizationContext.Current.Post(QueueFrob, 0);
    } 
    
    void QueueFrob(Object state) {
        var i = (int)state;
        FrobAsync(i);
        if (i == 99) return;
        SynchronizationContext.Current.Post(QueueFrob, i+1);
    }
    

    没有在每次迭代之间对DoEvents 的讨厌调用。发生这种情况的原因是,对 await 的调用发送了一条 Windows 消息,该消息将 FrobAsync 调用排入队列,从而允许在下一次 Frobbing 开始之前执行在 Frobbing 之间发生的任何 Windows 消息。

    【讨论】:

    • 其实更像DoEvents(); Frob();(假设FrobAsync异步调用了一个方法Frob()...
    【解决方案3】:

    async/await 只是关于异步。从调用方来看,是否使用多个线程并不重要——只是它做一些异步的事情。例如,IO 完成端口是异步的,但不执行任何多线程(操作的完成发生在后台线程上;但“工作”并未在该线程上完成)。

    从await 关键字来看,“多线程”是没有意义的。异步操作所做的是一个实现细节。

    在 Eric 的示例中,该方法可以按如下方式实现:

    return Task.Factory.StartNew(SomeMethod, 
                                 TaskScheduler.FromCurrentSynchronizationContext());
    

    这实际上意味着当它完成所有当前排队的工作时,在当前线程上对SomeMethod 的调用排队。这与FrobAsync 的调用者异步,因为FrobAsync 可能在SomeMethod 执行之前返回。

    现在,FrobAsync 可以实现为使用多线程,在这种情况下,它可以这样编写:

    return Task.Factory.StartNew(SomeMethod);
    

    如果默认 TaskScheduler 没有更改,则使用线程池。但是,从调用者的角度来看,什么都没有改变——你仍然是 await 方法。

    从多线程的角度来看,这是你应该看的。使用Task.Start、Task.Run 或Task.Factory.StartNew。

    【讨论】:

    • 我想我明白了。该模式根本不关心线程。字面上地。它知道如何在遇到等待的未完成任务时暂停“异步”方法,并且知道如何在等待的任务完成时恢复该方法。如果任务通过启动另一个线程来完成它的工作,很好,那个模式不知道/不在乎。如果它通过调用一些异步完成工作的 Windows API 来完成工作,很好,模式不知道/不在乎。如果它通过将其导出到某个外部系统来完成它的工作,那很好,模式不知道也不关心。
    • 关于我最初的问题是关于一个线程上的异步是否仅仅是学术性的;显然不是。显然,有相当多的 API 可以等待,它们确实可以有效地异步完成他们的工作,但这样做不需要启动单独的线程。
    • 正确,如果您正在转向 WinRT,那么有许多新的 Async 方法在 Win32(或 .NET)中具有仅同步对应物,但现在在后台线程上调用,因为原始同步方法可能需要超过 50 毫秒。 .NET 4.5 应该有类似的新方法。
    • 所以我绝对可以编写一个使用“async/await”的应用程序,而不必自己启动一个单独的线程。但是,如果我要卸载我自己的工作(在这种情况下,肯定没有预先编写的 API 来异步完成我的自定义工作),我将不得不弄清楚如何异步完成这项工作。这很可能会涉及 Task.Run 等。
    猜你喜欢
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 2012-11-26
    • 1970-01-01
    • 2012-09-08
    • 2010-09-17
    相关资源
    最近更新 更多