在没有第二个线程帮助的情况下异步执行代码

本文关键字:情况下 异步 执行 代码 帮助 第二个 线程 | 更新日期: 2023-09-27 17:55:22

好的,首先,我不需要实现什么。我只需要知道答案,因为更有经验的人告诉我,异步执行不一定必须涉及新线程,因为线程是有点繁重的结构,这让我很困惑,我不能同意。

现在比方说,我有两种方法-Execute()ExecuteAsync()Execute() 正在主线程上运行。我想从Execute()内部调用ExecuteAsync(),我不在乎它何时完成执行,但是当它完成时,可能是(也可能不是)我想使用它的返回值。这是异步执行的典型示例,对吧?

我知道我可以使用BackgroundWorkerIAsyncResultDelegate.BeginInvoke())来做到这一点,但是AFAIK在引擎盖下他们生成了一个辅助CLR线程/线程池线程。

那么,是否可以在没有第二个线程帮助的情况下异步ExecuteAsync()执行该方法呢?

编辑

我认为这次编辑将进一步澄清情况。调用ExecuteAsync()并不是Execute()要执行的唯一(或最后一个)任务。 Execute()应该继续自己的任务,而不关心ExecuteAsync()方法的执行。

在没有第二个线程帮助的情况下异步执行代码

下面是一个使用异步且从不使用多个线程的程序示例:

public class Foo
{
    private int _value;
    private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    public int Value
    {
        get
        {
            return _value;
        }
        set
        {
            _value = value;
            var oldTCS = tcs;
            tcs = new TaskCompletionSource<bool>();
            oldTCS.SetResult(true);
        }
    }

    public Task ValueChanged()
    {
        return tcs.Task;
    }
}
private static void Main(string[] args)
{
    Foo foo = new Foo();
    foo.ValueChanged()
        .ContinueWith(t =>
        {
            Console.WriteLine(foo.Value);
        }, TaskContinuationOptions.ExecuteSynchronously);
    foo.Value = 5;
}

ValueChanged返回的Task将在下次更改Value时完成。 Foo类的用户可以获取返回的任务,并连接延续以基于尚未发生的操作在该任务上运行。 然后,在将来的某个时候,foo的值会发生变化,并且延续将运行。 请注意,foo 对象可以传递给其他一些函数,Main 完全不知道,最终设置值(以显示为什么您可能想要做这样的事情)。

不需要

新的线程来创建Task,也不需要执行延续。

这是另一个更实用的例子:

我们将从这个简单的(扩展)方法开始,该方法采用一个表单并返回一个Task,指示该表单何时关闭:

public static class FormExtensions
{
    public static Task WhenClosed(this Form form)
    {
        var tcs = new TaskCompletionSource<bool>();
        form.FormClosed += (sender, args) => tcs.SetResult(true);
        return tcs.Task;
    }
}

现在我们可以在其中一个表单中拥有它:

private async void button1_Click(object sender, EventArgs args)
{
    Form2 otherForm = new Form2();
    otherForm.Show();
    await otherForm.WhenClosed();
    //take some data from that form and display it on this form:
    textBox1.Text = otherForm.Name;
}

创建和显示另一个窗体从不涉及创建新线程。 此窗体和新窗体都完全使用要创建和修改的一个 UI 线程。

创建从WhenClosed返回的Task根本不需要创建新线程。

等待Task时,不会创建新线程。 当前方法结束,UI 线程返回到处理消息。 在某些时候,同一个 UI 线程将执行导致第二个窗体关闭的操作。 这将导致任务继续运行,从而返回到设置文本框文本的按钮单击处理程序。

所有这些都完全由 UI 线程完成,没有创建其他线程。 然而,我们只是"等待"(没有实际等待)长时间运行的操作完成(用户将一些信息放入第二个窗体,然后关闭它),而不会阻塞 UI 线程,从而保持主窗体响应。

那么,在没有第二个线程帮助的情况下异步执行方法 ExecuteAsync() 是否有可能呢?

某些方法可以在不使用新线程的情况下异步运行。 例如,这可以通过带有信号的异步 I/O 来完成。 框架的大多数新异步方法都尽可能在 .NET 4.5 异步 IO 中添加,而不是线程。

这就是为什么不假设异步 == 新线程是个好主意。 虽然异步可以使用线程实现,但并不总是以这种方式实现。 最好将异步操作视为(希望)不会阻塞的操作,并且会在将来的某个时候完成。

协程是使用单个物理线程实现多个逻辑线程的常用方法。较旧的操作系统使用此概念和其他相关概念来实现协作式多任务处理。

在这种情况下,您可能还对延续传递风格感兴趣,Eric Lippert有一个关于这个主题的很好的博客系列 - 第1部分,第2部分,第3部分,第4部分,第5部分。