如何获取所示的第二种形式的同步上下文

发布于 2024-10-08 23:58:40 字数 2607 浏览 3 评论 0原文

[编辑]重新措辞和简化整个帖子[/编辑]

在此blog,下面(我稍微简化了一下)给出了使用 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;
            }
        }
    }
}

[EDIT] Rephrased and Simplified whole post [/EDIT]

In this blog, the following (I simplified it a bit) is given as an example of using a SynchronizationContext object to run a Task on the UI thread:

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

I can repeat these results in a fresh project, updating the UI safely, but for whatever reason in my current project (even though it's been working) I can't. I get the standard "You're not allowed to update the UI from the wrong thread" exception.

My code (in MainForm_Load(...)) is like this, which works in a fresh Project w/ a textBox1 added to the main form, but does not work in my current project:

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

Anyone have any thoughts on what might be gong on.

[EDIT]

I've traced the error back to the instantiation of an object which uses a form to prompt the user for login information. The error only happens when the form has been shown. (If I return a hardcoded value before that Form's Show happens the whole thing works fine).

New question: How can I get the SynchronizationContext for the form which I'm constructing if its own constructor displays another form before it has been shown? Here's how you can reproduce what's happening:

1) Create two forms: Form1 with a TextBox, and Form2 with a Button

2) Create a class 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;
            }
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

献世佛 2024-10-15 23:58:40

仅仅位于主线程上是不够的。您需要有一个有效的 SynchronizationContext.Current(在 FromCurrentSynchronizationContext 行上设置断点并检查 SynchronizationContext.Current 的值;如果它是 < code>null,那么就出了问题)。

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

编辑:

WinForms 中存在一个错误,将其放入 Form.Load 中也不够 - 您实际上必须通过读取 Handle 来强制创建 Win32 句柄 属性。我的印象是这个错误已经被修复了,但我可能是错的。

编辑2(从评论复制):

我怀疑您的问题是您在Application.Run之外调用ShowDialogShowDialog 是一个嵌套消息循环,但在本例中没有父消息循环。如果您在 SynchronizationContext.Current 上设置监视并单步执行 ShowDialog,您会在显示对话框之前看到它是一个 WindowsFormsSynchronizationContext,但显示对话框后更改为非 WinForms SynchronizationContext。将成员创建(包括 ShowDialog)移至 Load 事件可以解决该问题。

Just being on the main thread isn't sufficient. You need to have a valid SynchronizationContext.Current (set a breakpoint on the FromCurrentSynchronizationContext line and examine the value of SynchronizationContext.Current; if it's null, then something's wrong).

The cleanest fix is to execute your task code including FromCurrentSynchronizationContext from within the UI message loop - that is, from something like Form.Load for WinForms or Window.Loaded for WPF.

Edit:

There was a bug in WinForms where putting it in Form.Load wasn't sufficient either - you actually had to force Win32 handle creation by reading the Handle property. I was under the impression that this bug had been fixed, but I could be wrong.

Edit 2 (copied from comment):

I suspect your problem is that you're calling ShowDialog outside of Application.Run. ShowDialog is a nested message loop, but in this case there's no parent message loop. If you set a watch on SynchronizationContext.Current and step through the ShowDialog, you'll see that it's a WindowsFormsSynchronizationContext before the dialog is shown but changes to a non-WinForms SynchronizationContext after the dialog is shown. Moving the member creation (including the ShowDialog) to the Load event fixes the problem.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文