如何获取第二个表单所显示的同步上下文

4

[编辑] 重述并简化整篇文章 [/编辑]

在这篇博客中,以下内容(我稍微简化了一下)被作为使用SynchronizationContext对象在UI线程上运行任务的示例:

Task.Factory.StartNew(() =>"Hello World").ContinueWith(
            task => textBox1.Text = task.Result,
            TaskScheduler.FromCurrentSynchronizationContext());

我可以在一个新项目中重复这些结果,并安全地更新UI,但出于某种原因,在我的当前项目中(即使它一直工作),我无法这样做。我收到了标准的“您不被允许从错误的线程更新UI”的异常。

我的代码(在MainForm_Load(...)中)如下,在一个新的项目中添加了textBox1到主表单中,但在我的当前项目中无法工作:

var one = Task.Factory.StartNew(
        () => "Hello, my name is Inigo Montoya");
var two = one.ContinueWith(
        task => textBox1.Text = one.Result,
        TaskScheduler.FromCurrentSynchronizationContext());

请问大家对于可能发生的情况有何想法。

[编辑]

我已经追踪到了一个对象实例化的错误,该对象使用表单提示用户输入登录信息。只有在表单显示后才会出现错误。(如果在该表单的Show之前返回硬编码值,则整个过程正常运行)。

新问题:如果构造函数在显示另一个表单之前,如何获取正在构建的表单的SynchronizationContext呢?以下是重现问题的方法:

1)创建两个表单:带有TextBox的Form1和带有Button的Form2。

2)创建一个名为OwnedBy1Uses2的类。

Form1

public partial class Form1 : Form
{
    OwnedBy1Uses2 member;
    public Form1()
    {
        InitializeComponent();
        member = new OwnedBy1Uses2();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        var ui = TaskScheduler.FromCurrentSynchronizationContext();
        Task<string> getData = Task.Factory.StartNew(
            () => "My name is Inigo Montoya...");
        Task displayData = getData.ContinueWith(
            t => textBox1.Text = t.Result, ui);
    }
}

Form2:

public partial class Form2 : Form
{
    public Form2()
    {
        InitializeComponent();
        DialogResult = System.Windows.Forms.DialogResult.Cancel;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        DialogResult = System.Windows.Forms.DialogResult.OK;
        Hide();
    }
}

OwnedBy1Uses2:

class OwnedBy1Uses2
{
    int x;
    public OwnedBy1Uses2()
    {
        using (Form2 form = new Form2())
        {
            if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                x = 1;
            }
            else
            {
                x = 2;
            }
        }
    }
}

我认为任务的整个目的是异步运行,根据定义应该在不同的线程中运行。正如您所知,您无法从另一个线程访问控件。 - Tony The Lion
更多细节:我正在异步地从数据库加载对象(这是一个非常长时间运行的过程)。当完成后,我定义了“displayItems”作为一个非常快速运行的过程来更新UI。任务的另一个重点是管理异步进程/线程的时间和顺序。这就是displayItems任务在这里所做的:“当previousTask完成时,显示已加载的项目”。 - Chris Pfohl
如果您正在使用System.Threading.Task命名空间,则任务将使用CLR线程池线程,因此这不再是您的UI线程。请查看此链接:http://www.eggheadcafe.com/tutorials/aspnet/21013a52-fe11-4af8-bf8b-50cfd1a51577/task-parallelism-in-c-4.aspx - Tony The Lion
除了这段代码两个小时前还能完美运行。 - Chris Pfohl
我在阅读了你提供的那些文章之后,并没有发现你代码中有太多问题。你还有其他类似的代码吗?如果有,能否贴出来? - Tony The Lion
@Cpfohl:这段代码看起来没有问题,所以肯定是你项目中的其他因素导致了问题,如果可以的话,请发更多与该问题相关的项目代码? - Tony The Lion
1个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
5

仅仅在主线程上运行任务是不足够的。你需要有一个有效的 SynchronizationContext.Current (在 FromCurrentSynchronizationContext 行上设置断点并检查 SynchronizationContext.Current 的值;如果是 null,那么就有问题)。

最干净的解决方法是从 UI 消息循环中执行包括 FromCurrentSynchronizationContext 的任务代码 - 即从像 WinForms 的 Form.Load 或 WPF 的 Window.Loaded 中执行。

编辑:

在 WinForms 中有一个错误,在 Form.Load 中放置它也不足够 - 你实际上必须通过读取 Handle 属性来强制 Win32 句柄创建。我以为这个错误已经被修复了,但我可能是错的。

编辑2(摘自评论):

我怀疑你的问题是你在 Application.Run 外部调用 ShowDialogShowDialog 是一个嵌套的消息循环,但在这种情况下没有父消息循环。 如果您在 Load 事件中设置成员创建(包括 ShowDialog),则可以解决该问题。通过设置监视器在 ShowDialog 中步进并查看 SynchronizationContext.Current,您会发现在显示对话框之前它是一个 WindowsFormsSynchronizationContext,但在显示对话框后变成了非 WinForms 的 SynchronizationContext


我的代码目前正在从UI消息循环中执行......它在TreeView SelectedChanged事件的自动生成处理程序中。此外,当我断点时,我得到了一个值,它不仅仅是null。它也是直接从Form.Load获取的。 - Chris Pfohl
我的确切代码有几千行......我会努力获取一个可以单独运行的副本。我已经将错误缩小到创建一个对象,该对象在主要对象创建之前创建另一个表单。这是否会以某种方式破坏“SynchronizationContext.Current”?(这是一个登录表单,并且需要在此之前显示) - Chris Pfohl
糟糕,肯定是这个问题!我已经硬编码了我的登录数据一段时间了,但最近我手动重新启用了它...现在它不起作用了... - Chris Pfohl
我怀疑你的问题是在Application.Run之外调用了ShowDialogShowDialog是一个嵌套消息循环,但在这种情况下没有父级消息循环。如果你在SynchronizationContext.Current上设置一个监视器并逐步执行ShowDialog,你会发现在显示对话框之前它是一个WindowsFormsSynchronizationContext,但在显示对话框后变成了非WinForms的SynchronizationContext。将成员创建(包括ShowDialog)移动到Load事件中可以解决这个问题。 - Stephen Cleary
非常感谢!一旦暴风雨停了,同事打开了我的电脑,我就会远程桌面连接并尝试一下!谢谢! - Chris Pfohl
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,