如何获取所示的第二种形式的同步上下文
[编辑]重新措辞和简化整个帖子[/编辑]
在此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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
仅仅位于主线程上是不够的。您需要有一个有效的
SynchronizationContext.Current
(在FromCurrentSynchronizationContext
行上设置断点并检查SynchronizationContext.Current
的值;如果它是 < code>null,那么就出了问题)。最干净的解决方法是从 UI 消息循环中执行包括
FromCurrentSynchronizationContext
在内的任务代码 - 即,从 WinForms 的Form.Load
或 Window.Loaded 等内容执行 对于 WPF。编辑:
WinForms 中存在一个错误,将其放入
Form.Load
中也不够 - 您实际上必须通过读取Handle 来强制创建 Win32 句柄
属性。我的印象是这个错误已经被修复了,但我可能是错的。编辑2(从评论复制):
我怀疑您的问题是您在
Application.Run
之外调用ShowDialog
。ShowDialog
是一个嵌套消息循环,但在本例中没有父消息循环。如果您在SynchronizationContext.Current
上设置监视并单步执行ShowDialog
,您会在显示对话框之前看到它是一个WindowsFormsSynchronizationContext
,但显示对话框后更改为非 WinFormsSynchronizationContext
。将成员创建(包括ShowDialog
)移至Load
事件可以解决该问题。Just being on the main thread isn't sufficient. You need to have a valid
SynchronizationContext.Current
(set a breakpoint on theFromCurrentSynchronizationContext
line and examine the value ofSynchronizationContext.Current
; if it'snull
, 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 likeForm.Load
for WinForms orWindow.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 theHandle
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 ofApplication.Run
.ShowDialog
is a nested message loop, but in this case there's no parent message loop. If you set a watch onSynchronizationContext.Current
and step through theShowDialog
, you'll see that it's aWindowsFormsSynchronizationContext
before the dialog is shown but changes to a non-WinFormsSynchronizationContext
after the dialog is shown. Moving the member creation (including theShowDialog
) to theLoad
event fixes the problem.