Конечно, вы не можете ссылаться на Control, созданный в UI Thread, из другого потока; как вы заметили, возникает исключение Invalid Cross-thread operation
, когда вы пытаетесь: Приложение Windows Forms должно быть однопоточным, причины хорошо объяснены в документации STAThreadAttribute Class .
Примечание : удалите все Console.ReadLine()
, вы не можете использовать это в WinForms (нет консоли).
Вот некоторые реализации, которые могут работать для вас, в порядок релевантности в вашем контексте (ну, по крайней мере, я так думаю. Вы выбираете то, что предпочитаете).
► Progress<T>
: этот класс действительно прост в использовании. Вам просто нужно определить его возвращаемый тип (тип T
, это может быть что угодно, простой string
, объект класса и т.д. c.). Вы можете определить его на месте (где вы вызываете свои многопоточные методы) и передать ссылку на него. Вот и все. Метод, который получает ссылку, вызывает свой метод Report () , передавая значения, определенные в T
. Этот метод выполняется в потоке, создавшем объект Progress<T>
. Как видите, вам не нужно передавать ссылку Control на GetQueue()
:
Сторона формы:
// [...]
var progress = new Progress<string>(msg => listBox1.Items.Add(msg));
Azure.GetQueue(progress);
// [...]
Azure сторона класса:
public static void GetQueue(IProgress<string> update)
{
// [...]
try {
queueClient.OnMessage(message => {
string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
update.Report(body);
message.Complete();
});
}
// [...]
}
► SynchronizationContext (WindowsFormsSynchronizationContext
) Post () : этот класс используется для синхронизации c контекстов потоковой передачи, его метод Post()
отправляет асинхронное сообщение для синхронизации контекст, в котором создается объект класса, на который ссылается свойство Current . Конечно, см. Параллельные вычисления - все о контексте синхронизации .
Реализация не сильно отличается от предыдущей: вы можете использовать Lambda в качестве SendOrPostCallback
делегата сообщения ( ) метод. Делегат Action<string>
используется для публикации в потоке пользовательского интерфейса без необходимости передавать ссылку на элемент управления методу Azure.GetQueue()
:
Сторона формы:
// Add static Field for the SynchronizationContext object
static SynchronizationContext sync = null;
// Add a method that will receive the Post() using an Action delegate
private void Updater(string message) => listBox1.Items.Add(message);
// Call the method from somewhere, passing the current sync context
sync = SynchronizationContext.Current;
Azure.GetQueue(sync, Updater);
// [...]
Azure класс сторона:
public static void GetQueue(SynchronizationContext sync, Action<string> updater)
{
// [...]
try {
queueClient.OnMessage(message => {
string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
sync.Post((spcb) => { updater(body); }, null);
message.Complete();
});
}
// [...]
}
► Control.BeginInvoke () : вы можете использовать BeginInvoke()
для асинхронного выполнения делегата (обычно как лямбда-выражения) в потоке, создавшем дескриптор Control. Конечно, вы должны передать ссылку Control на метод Azure.GetQueue()
. Вот почему в этом случае у этого метода есть меньшее предпочтение (но вы все равно можете его использовать).
BeginInvoke()
не требует проверки Control.InvokeRequired
: этот метод можно вызвать из любого потока, включая поток пользовательского интерфейса. Для вызова Invoke()
вместо этого требуется эта проверка, поскольку она может вызвать тупик, если используется из потока пользовательского интерфейса
Сторона формы:
Azure.GetQueue(this, Updater);
// [...]
// Add a method that will act as the Action delegate
private void Updater(string message) => listBox1.Items.Add(message);
Azure сторона класса:
public static void GetQueue(Control control, Action<string> action)
{
// [...]
try {
queueClient.OnMessage(message => {
string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
control.BeginInvoke(new Action(()=> action(body));
message.Complete();
});
}
// [...]
}
Вы также можете использовать System. Windows .Threading.Dispatcher для управления рабочими элементами потока в очереди, вызывая его методы BeginInvoke()
(предпочтительно) или Invoke()
. Его реализация похожа на метод SynchronizationContext
, и его методы называются уже упомянутым методом Control.BeginInvoke()
.
Я не реализую его здесь, поскольку Dispatcher требует ссылки на WindowsBase.dll
(WPF , обычно), и это может вызвать нежелательные эффекты в приложении WinForms, которое не является DpiAware. Об этом можно прочитать здесь: Осведомленность о DPI - не знает в одной версии, осведомлена о системе в другой
В любом случае, если вам интересно, дайте мне знать.