#c# #multithreading #events #delegates #backgroundworker
#c# #многопоточность #Мероприятия #делегаты #backgroundworker
Вопрос:
У меня есть приложение (frmMain), которое вызывает класс (ThreadBL), который запускает 2 потока (Thread1, Thread2). Когда Thread1 выполняет операции, я хочу иметь возможность отправлять обновления обратно через frmMain, аналогично Thread2 будет делать то же самое.
Вот несколько сокращенных кодов, которые в основном показывают, как это работает. У меня не было возможности проверить, работает ли этот конкретный код, но когда я запускаю то, что у меня есть, исходный код, я получаю ошибку «Операция с перекрестным потоком недопустима при многопоточности».
Есть ли лучший способ обновить frmMain из потоков? Является ли этот код слишком исчерпывающим и ненужным? Мы высоко ценим любые отзывы.
public class ThreadExample() {
private void ThreadExample() {};
public delegate void CurrentFileProcessing(string filename);
public event CurrentFileProcessing CurrentFileProcessingEvent;
public bool startCopying() {
CurrentFileProcessingEvent = new CurrentFileProcessing(handlerCurrentFileProcessing);
copyFiles();
return true;
}
public void copyFiles() {
CurrentFileProcessingEvent("Copying: file.xml");
}
private void handlerCurrentFileProcessing(string filename) {
Console.WriteLine("Processing: " filename);
}
}
public class ThreadBL() {
private void ThreadBL() {};
public delegate void Thread1CurrentProcessing(string filename);
public delegate void Thread2CurrentProcessing(string filename);
public event Thread1CurrentProcessing Thread1CurrentProcessingEvent;
public event Thread2CurrentProcessing Thread2CurrentProcessingEvent;
private bool processingThread1 = false;
private bool processingThread2 = false;
public void processThreads() {
BackgroundWorker thread1BW = new BackgroundWorker();
thread1BW.DoWork = new DoWorkEventHandler(thread1Process);
thread1BW.RunWorkerCompleted = new RunWorkerCompletedEventHandler(completeThread1);
thread1BW.RunWorkerAsync();
while (!processingThread1) {
Console.WriteLine("Waiting for thread1 to finish. TID: " Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
}
BackgroundWorker thread2BW = new BackgroundWorker();
thread2BW.DoWork = new DoWorkEventHandler(thread2Process);
thread2BW.RunWorkerCompleted = new RunWorkerCompletedEventHandler(completeThread2);
thread2BW.RunWorkerAsync();
while (!thread2Done) {
Console.WriteLine("Waiting for thread2 to finish. TID: " Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(100);
}
}
private void thread1Process() {
ThreadExample thread1Example = new ThreadExample();
thread1Example.CurrentFileProcessingEvent = new ThreadExample.CurrentFileProcessing(handlerThread1CurrentProcessingEvent);
processingThread1 = thread1Example.startCopying();
}
private void completeThread1(object sender, RunWorkerCompletedEventArgs e) {
Console.WriteLine("Completed Thread1. TID: " Thread.CurrentThread.ManagedThreadId);
processingThread1 = true;
}
private void thread2Process() {
ThreadExample thread2Example = new ThreadExample();
thread2Example.CurrentFileProcessingEvent = new ThreadExample.CurrentFileProcessing(handlerThread2CurrentProcessingEvent);
processingThread2 = thread2Example.startCopying();
}
private void completeThread2(object sender, RunWorkerCompletedEventArgs e) {
Console.WriteLine("Completed Thread1. TID: " Thread.CurrentThread.ManagedThreadId);
processingThread2 = true;
}
private void handlerThread2CurrentProcessingEvent(string filename) {
Console.WriteLine("Thread2 Processing: " filename);
Thread2CurrentProcessingEvent(filename);
}
}
public class frmMain {
private ThreadBL threadBL = new ThreadBL();
public void frmMain() {
threadBL.Thread1CurrentProcessingEvent = new ThreadExample.CurrentFileProcessing(handlerThread1ProgressEvent);
threadBL.Thread2CurrentProcessingEvent = new ThreadExample.CurrentFileProcessing(handlerThread2ProgressEvent);
threadBL.processThreads();
}
private void handlerThread1ProgressEvent(string progress) {
lblCopyingProgress.Invoke(new MethodInvoker(delegate { lblCopyingProgress.Text = progress; }));
this.Refresh();
}
private void handlerThread2ProgressEvent(string progress) {
lblCopyingProgress.Invoke(new MethodInvoker(delegate { lblCopyingProgress.Text = progress; }));
this.Refresh();
}
}
Ответ №1:
Лучший способ вызвать основной поток — это вызвать Control.Вызвать (…) или элемент управления.BeginInvoke(…). Первая будет блокироваться до тех пор, пока основной поток не обработает вызов. Последняя просто отправит вызов, который будет обработан, когда основной поток освободится.
Если вы не хотите, чтобы ваши потоки знали о типе элемента управления, вы можете просто перенести вызовы Invoke и BeginInvoke в свой собственный интерфейс, скажем, IInvoker, и объявить вашу основную форму как ее реализацию. Передайте интерфейс в качестве параметра вашего потока, и все готово.
Для выполнения потоковой работы я рекомендую использовать ThreadPool. Я бы сделал что-то вроде этого (предположим, что все методы находятся в коде вашей основной формы).
private void MyThread(object param)
{
MyForm form = (MyForm) param; // pass your form as your param
DoWork(); // Whatever it is you are doing on your thread
form.Invoke(new MethodInvoker(form.NotifyComplete)); // Invokes on main thread
}
public void Button_OnClick(object sender, EventArgs args)
{
ThreadPool.QueueUserWorkItem(new Action<object>(MyThread), this);
}
private void NotifyComplete()
{
// update your controls here
...
}
Комментарии:
1. Спасибо за ваш ответ. Итак, как бы вы это реализовали, используя мой пример выше?