在用户界面和控制台应用程序中使用 Task.Yield() 之间的区别

2024-02-26

我试图异步显示一个进度表单,表明应用程序正在运行,而实际应用程序正在运行。

如下这个问题 https://stackoverflow.com/questions/33406939/async-showdialog,我有以下内容:

主要形式:

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    async Task<int> LoadDataAsync()
    {
        await Task.Delay(2000);
        return 42;
    }

    private async void Run_Click(object sender, EventArgs e)
    {
        var runningForm = new RunningForm();

        runningForm.ShowRunning();

        var progressFormTask = runningForm.ShowDialogAsync();

        var data = await LoadDataAsync();

        runningForm.Close();
        await progressFormTask;

        MessageBox.Show(data.ToString());
    }
}

进度表

public partial class RunningForm : Form
{
    private readonly SynchronizationContext synchronizationContext;

    public RunningForm()
    {
        InitializeComponent();
        synchronizationContext = SynchronizationContext.Current;
    }

    public async void ShowRunning()
    {
        this.RunningLabel.Text = "Running";
        int dots = 0;

        await Task.Run(() =>
        {
            while (true)
            {
                UpadateUi($"Running{new string('.', dots)}");

                Thread.Sleep(300);

                dots = (dots == 3) ? 0 : dots + 1;
            }
        });
    }

    public void UpadateUi(string text)
    {
        synchronizationContext.Post(
            new SendOrPostCallback(o =>
            {
                this.RunningLabel.Text = text;
            }),
            text);
    }

    public void CloseThread()
    {
        synchronizationContext.Post(
            new SendOrPostCallback(o =>
            {
                this.Close();
            }),
            null);
    }
}

internal static class DialogExt
{
    public static async Task<DialogResult> ShowDialogAsync(this Form form)
    {
        await Task.Yield();
        if (form.IsDisposed)
        {
            return DialogResult.OK;
        }
        return form.ShowDialog();
    }
}

上面的工作正常,但当我从另一个地方打电话时,它不起作用。这是我的控制台应用程序:

class Program
{
    static void Main(string[] args)
    {
        new Test().Run();
        Console.ReadLine();
    }
}

class Test
{
    private RunningForm runningForm;

    public async void Run()
    {
        var runningForm = new RunningForm();

        runningForm.ShowRunning();

        var progressFormTask = runningForm.ShowDialogAsync();

        var data = await LoadDataAsync();

        runningForm.CloseThread();

        await progressFormTask;

        MessageBox.Show(data.ToString());
    }

    async Task<int> LoadDataAsync()
    {
        await Task.Delay(2000);
        return 42;
    }
}

观察调试器发生的情况,进程开始await Task.Yield()并且永远不会进展到return form.ShowDialog()因此你永远不会看到RunningForm。然后该过程转到LoadDataAsync()并永远挂在await Task.Delay(2000).

为什么会发生这种情况?和怎么做有关系吗Tasks 具有优先级(即:Task.Yield() https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.yield?view=netframework-4.7.2)?


观察调试器发生的情况,进程开始等待 Task.Yield() 并且永远不会返回 form.ShowDialog() ,因此 你永远不会看到 RunningForm。然后该过程转到 LoadDataAsync() 并永远挂在等待 Task.Delay(2000) 上。

为什么会发生这种情况?

这里发生的事情是当你这样做时var runningForm = new RunningForm()在没有任何同步上下文的控制台线程上(System.Threading.SynchronizationContext.Current为 null),它隐式创建一个实例WindowsFormsSynchronizationContext并将其安装在当前线程上,更多信息在此here https://stackoverflow.com/q/19535147/1768303.

然后,当你击中await Task.Yield(), the ShowDialogAsync方法返回给调用者并且await延续被发布到新的同步上下文。但是,延续永远不会有机会被调用,因为当前线程不运行消息循环,并且发布的消息不会被泵送。没有死锁,但是后面的代码await Task.Yield()永远不会执行,因此对话框甚至不会显示。同样的道理await Task.Delay(2000).

我更感兴趣的是了解为什么它适用于 WinForms 而不是 控制台应用程序。

您的控制台应用程序中需要一个带有消息循环的 UI 线程。尝试像这样重构您的控制台应用程序:

public void Run()
{
    var runningForm = new RunningForm();
    runningForm.Loaded += async delegate 
    {
        runningForm.ShowRunning();

        var progressFormTask = runningForm.ShowDialogAsync();

        var data = await LoadDataAsync();

        runningForm.Close();

        await progressFormTask;

        MessageBox.Show(data.ToString());
    };
    System.Windows.Forms.Application.Run(runningForm);
}

在这里,工作Application.Run是启动模式消息循环(并安装WindowsFormsSynchronizationContext在当前线程上)然后显示表单。这runningForm.Loaded异步事件处理程序在该同步上下文上调用,因此其中的逻辑应该按预期工作。

然而这使得Test.Run 同步方法, 我。即,它仅在表单关闭且消息循环结束时返回。如果这不是您想要的,您必须创建一个单独的线程来运行消息循环,就像我所做的那样with MessageLoopApartment here https://stackoverflow.com/a/22262976/1768303.

也就是说,在典型的 WinForms 或 WPF 应用程序中,您几乎不需要辅助 UI 线程。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在用户界面和控制台应用程序中使用 Task.Yield() 之间的区别 的相关文章

随机推荐

  • Python ConfigParser,键中带有冒号

    如何在 python configparser 中的值中添加分号 https stackoverflow com questions 4298073 how do i put a semicolon in a value in python
  • 响应式设计像素与百分比

    我希望有人能提供帮助 我最近在一个知名网站上观看了有关响应式网页设计的视频 我对导师采取的方法感到困惑 他基本上创建了三个样式表 大 中 小 在每个样式表中 他给出了 DIVS 的 PX 宽度 并在必要时交换图像 当然 这种方法是有缺陷的
  • 尝试使用 Jersey 创建 Restful WebService 时出错

    我正在关注this http www vogella com articles REST article html first创建 CRUD RESTful WebService 的教程 这种类型的 WebService 的教程从第 7 段
  • 找不到 css 文件 - ASP.NET Core Web 应用程序

    我已经创建了一个 asp net core Web 应用程序 我从 空 选项开始 并且正在逐步构建它 看来我的 css 文件没有被读取或找到 当我启动应用程序时 我可以看到我的 html 页面看起来不正常 当我在控制台下的 Edge 中使用
  • Rails 使用哪些测试框架? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 Rails 使用哪些单元测试框架 我正在阅读这本书 Pragmatic Programmers Agile Dev with Rails 它展示
  • 在 Rails 中启用自定义格式化程序

    我为 Rails 编写了一个自定义格式化程序 module Logging class GeneralFormatter lt Logger Formatter def call severity time program name mes
  • DialogFragment - 设置初始值并在旋转后保留状态

    我创建了一个对话框片段正在建设和回归警报对话框 from 创建对话框时方法 AlertDialog 包含两个 EditText 视图 我正在设置这两个编辑文本的初始值创建对话框时该方法非常有效 直到我旋转手机并且所有更改都会丢失 恢复为初始
  • Mongodb findOneAndUpdate 函数正在插入新文档

    每当我进行更新 api 调用时 我只需要更新 serviceActiveFlag 状态 更新 api 调用后 我可以看到创建了一个带有空车辆数组的新文档 如下所示 id 59c76073c11d3929148f500f vehicle Ar
  • 应用过滤器时,宽数据表导致scrollx向后滚动

    我能够在我闪亮的应用程序中渲染数据表 但是 只要有宽表 当您对后面的列应用过滤器时 水平滚动条就会回到其初始位置 仅数字列会出现此问题 我想知道是否有一种方法可以禁用基于范围的过滤器 但保留过滤器本身 或者是否有任何其他解决方法可以解决此问
  • Oracle INSERT ALL with SELECT 给出无效规范错误

    我想使用 INSERT ALL 语句将 2 行插入 2 个不同的表中 但如果我想自己插入值 下面的查询就可以正常工作 insert all into REGIONS values 5 Africa into JOBS values ZZZZ
  • Bootstrap-datetimepicker - 动态添加显示在错误的区域

    所以通过ajax写入DB之后 我的成功函数 构建一个元素并将其附加到表中 然后 将其附加到表后 它会在新附加的行上调用日期时间选择器 然而 日期时间选择器 日历 显示在完全奇怪的地方 请参阅屏幕截图 我应该提到的一件事是这些注释位于选项卡窗
  • Pandas - 替换列值

    我知道这个问题有很多主题 但没有一种方法对我有用 所以我发布了我的具体情况 我有一个如下所示的数据框 data pd DataFrame 1 0 0 1 1 0 0 1 columns sex split data sex replace
  • 将linux的.so文件转换为windows的.dll文件

    我有一些 C 代码 具有各种头文件和 make 文件 编译为 soLinux Ubuntu 中的文件和python使用以下方式调用此共享对象的函数的程序Ctypes 现在 我想在 Windows 上使用这个程序 在窗户里 Ctypes需要一
  • 在 WPF 中的组合框顶部有一个可单击的按钮

    我可以在组合框的下拉列表中显示一个按钮并可单击 但无法让选定的组合框项目 下拉列表已关闭 使该按钮可单击 它总是跳过按钮单击并仅打开下拉列表 我基本上希望我设置的 Button Click 事件处理程序在单击后被调用 这是我的示例组合框 它
  • Yii2 如何将 SUM SQL 函数转换为查询生成器?

    我有这个简单的 SQL 查询 SELECT product name SUM product amount FROM orders GROUP BY product name 它将显示一个包含产品名称及其数量的列表 就像这个例子 我想将其转
  • Markdown 创建页面和目录?

    我开始用markdown来做笔记 I use marked查看我的 Markdown 笔记 它很漂亮 但随着我的笔记变得越来越长 我发现很难找到我想要的东西 我知道 Markdown 可以创建表格 但是它是否能够创建目录 跳转到部分或在 M
  • 对最后 X 秒内收到的值的平均值进行采样

    我有一个调度成功和失败事件的类 我需要维护该类最后 X 秒内的平均失败数 事件总数的统计数据 我正在考虑使用循环链表并为每个事件附加成功或失败节点 然后计算列表中故障节点的数量与总节点数 但这有两个主要缺点 我需要不断地放大 缩小列表大小
  • PyQt4 创建简单 GUI 应用程序时遇到问题

    所以我正在使用 Python 和 PyQt4 创建一个简单的 Windows 应用程序 我在 QtCreator 中按照我想要的方式设计了 UI 并从 ui 文件创建了必要的 py 文件 然而 当我尝试实际打开窗口的实例时 出现以下错误 A
  • Angular 2:将数据传递给路由?

    我正在开发这个 angular2 项目 我正在使用ROUTER DIRECTIVES从一个组件导航到另一组件 有 2 个组件 IE PagesComponent DesignerComponent 我想从页面组件导航到设计组件 到目前为止
  • 在用户界面和控制台应用程序中使用 Task.Yield() 之间的区别

    我试图异步显示一个进度表单 表明应用程序正在运行 而实际应用程序正在运行 如下这个问题 https stackoverflow com questions 33406939 async showdialog 我有以下内容 主要形式 publ