How to get a Synchronization Context for the secon

2019-05-12 00:12发布

[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;
            }
        }
    }
}

1条回答
SAY GOODBYE
2楼-- · 2019-05-12 00:49

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.

查看更多
登录 后发表回答