Как получить контекст синхронизации для показанной второй формы
[РЕДАКТИРОВАТЬ] Перефразированный и упрощенный весь пост [/EDIT]
В этом блоге в качестве примера использования объекта SynchronizationContext для выполнения Задачи в потоке пользовательского интерфейса приводится следующее (я немного упростил его):
Task.Factory.StartNew(() =>"Hello World").ContinueWith(
task => textBox1.Text = task.Result,
TaskScheduler.FromCurrentSynchronizationContext());
Я могу повторить эти результаты в новом проекте, безопасно обновив пользовательский интерфейс, но по какой-либо причине в моем текущем проекте (даже если он работает) я не могу. Я получаю стандартное исключение "Вам не разрешено обновлять пользовательский интерфейс из неправильного потока".
Мой код (в 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) Создайте две формы: Form1 с TextBox
и Form2 с Button
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;
}
}
}
}
1 ответ
Просто быть в главном потоке недостаточно. Вы должны иметь действительный SynchronizationContext.Current
(установите точку останова на FromCurrentSynchronizationContext
линии и изучить значение SynchronizationContext.Current
; если это null
тогда что-то не так).
Самое чистое решение - выполнить код задачи, включая FromCurrentSynchronizationContext
из цикла сообщений пользовательского интерфейса - то есть из чего-то вроде Form.Load
для WinForms или Window.Loaded
для WPF.
Редактировать:
В WinForms была ошибка, из-за которой Form.Load
тоже было недостаточно - вам действительно пришлось заставить Win32 создавать дескрипторы, читая Handle
имущество. У меня сложилось впечатление, что эта ошибка была исправлена, но я могу ошибаться.
Редактировать 2 (скопировано из комментария):
Я подозреваю, что ваша проблема в том, что вы звоните ShowDialog
вне Application.Run
, ShowDialog
является вложенным циклом сообщений, но в этом случае родительский цикл сообщений отсутствует. Если вы установите часы на SynchronizationContext.Current
и пройти через ShowDialog
вы увидите, что это WindowsFormsSynchronizationContext
до отображения диалогового окна, но изменяется на не-WinForms SynchronizationContext
после того, как диалог показан. Перемещение создания члена (включая ShowDialog
) к Load
событие решает проблему.