Как правильно подождать до тех пор, пока BackgroundWorker не завершится?
Соблюдайте следующий фрагмент кода:
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnAsyncOperationCompleted;
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
Теперь предположим, что я хочу подождать, пока bw
не закончит работу. Каков правильный способ сделать это?
Мое решение таково:
bool finished = false;
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
OnAsyncOperationCompleted(sender, args);
finished = true;
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
int timeout = N;
while (!finished && timeout > 0)
{
Thread.Sleep(1000);
--timeout;
}
if (!finished)
{
throw new TimedoutException("bla bla bla");
}
Но мне это не нравится.
Я рассмотрел замену флага finished
событием синхронизации, установил его в обработчике RunWorkerCompleted
и заблокировал на нем позже вместо выполнения цикла while-sleep.
Увы, это неправильно, потому что код может работать в контексте синхронизации WPF или WindowsForm, и в этом случае я бы заблокировал тот же поток, что и обработчик RunWorkerCompleted
, что явно не очень умное движение.
Я хотел бы узнать о лучшем решении.
Спасибо.
EDIT:
P.S.
- Образец кода настолько умышленно проясняет мой вопрос. Я прекрасно понимаю обратный вызов завершения и все же хочу знать, как подождать до завершения. Это мой вопрос.
- Я знаю
Thread.Join
, Delegate.BeginInvoke
, ThreadPool.QueueUserWorkItem
и т.д. Вопрос конкретно о BackgroundWorker
.
ИЗМЕНИТЬ 2:
ОК, я думаю, это будет намного проще, если я объясню сценарий.
У меня есть метод unit test, который вызывает некоторый асинхронный код, который, в свою очередь, в конечном итоге включает BackgroundWorker
, с которым я могу передать обработчик завершения. Весь код мой, поэтому я могу изменить реализацию, если захочу.
Однако я не собираюсь заменять BackgroundWorker
, потому что он автоматически использует правильный контекст синхронизации, так что, когда код вызывается в потоке пользовательского интерфейса, обратный вызов завершения вызывается в одном и том же потоке пользовательского интерфейса, что очень хорошо.
Во всяком случае, возможно, что метод unit test заканчивается до того, как BW завершит свою работу, что не очень хорошо. Поэтому я хочу подождать, пока BW не завершится, и хотел бы знать лучший способ для этого.
В нем больше штук, но общая картина более или менее похожа на то, что я только что описал.
Ответы
Ответ 1
Попробуйте использовать класс AutoResetEvent следующим образом:
var doneEvent = new AutoResetEvent(false);
var bw = new BackgroundWorker();
bw.DoWork += (sender, e) =>
{
try
{
if (!e.Cancel)
{
// Do work
}
}
finally
{
doneEvent.Set();
}
};
bw.RunWorkerAsync();
doneEvent.WaitOne();
Предостережение: Вы должны убедиться, что doneEvent.Set()
вызывается независимо от того, что происходит. Также вы можете указать doneEvent.WaitOne()
аргумент, определяющий период ожидания.
Примечание: Этот код в значительной степени является копией Fredrik Kalseth для ответа на аналогичный вопрос.
Ответ 2
Чтобы подождать рабочий рабочий поток (один или несколько), выполните следующие действия:
-
Создайте список фоновых работников, которые вы запрограммировали:
private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
-
Добавьте в список фонового работника:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.WorkerReportsProgress = true;
m_WorkersWithData.Add(worker);
worker.RunWorkerAsync();
-
Используйте следующую функцию, чтобы ждать всех рабочих в Списке:
private void CheckAllThreadsHaveFinishedWorking()
{
bool hasAllThreadsFinished = false;
while (!hasAllThreadsFinished)
{
hasAllThreadsFinished = (from worker in m_WorkersWithData
where worker.IsBusy
select worker).ToList().Count == 0;
Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it
//from the Progress event of the background worker.
Thread.Sleep(1000); //This call waits if the loop continues making sure that the CPU time gets freed before
//re-checking.
}
m_WorkersWithData.Clear(); //After the loop exits clear the list of all background workers to release memory.
//On the contrary you can also dispose your background workers.
}
Ответ 3
BackgroundWorker имеет событие завершения. Вместо ожидания вызовите оставшийся путь кода из обработчика завершения.
Ответ 4
Этот вопрос старый, но я не думаю, что автор получил ответ, который он искал.
Это немного грязно, и это в vb.NET, но работает для меня
Private Sub MultiTaskingForThePoor()
Try
'Start background worker
bgwAsyncTasks.RunWorkerAsync()
'Do some other stuff here
For i as integer = 0 to 100
lblOutput.Text = cstr(i)
Next
'Wait for Background worker
While bgwAsyncTasks.isBusy()
Windows.Forms.Application.DoEvents()
End While
'Voila, we are back in sync
lblOutput.Text = "Success!"
Catch ex As Exception
MsgBox("Oops!" & vbcrlf & ex.Message)
End Try
End Sub
Ответ 5
VB.NET
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
Вы можете использовать это для объединения нескольких событий. (код sudo для последующего)
download_file("filepath")
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
'Waits to install until the download is complete and lets other UI events function install_file("filepath")
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
'Waits for the install to complete before presenting the message box
msgbox("File Installed")
Ответ 6
Проверка backgrWorker.IsBusy
в цикле с помощью Application.DoEvents()
не является хорошим способом.
Я согласен с @JohannesH, вы должны окончательно использовать AutoResetEvent в качестве элегантного решения. Но не используя его в потоке пользовательского интерфейса, это приведет к блокировке основного потока; он должен исходить из другой рабочей рабочей ветки.
AutoResetEvent aevent = new AutoResetEvent(false);
private void button1_Click(object sender, EventArgs e)
{
bws = new BackgroundWorker();
bws.DoWork += new DoWorkEventHandler(bw_work);
bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete);
bws.RunWorkerAsync();
bwWaiting.DoWork += new DoWorkEventHandler(waiting_work);
bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete);
bwWaiting.RunWorkerAsync();
}
void bw_work(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
}
void bw_complete(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("complete " + bwThread.ToString());
aevent.Set();
}
void waiting_work(object sender, DoWorkEventArgs e)
{
aevent.WaitOne();
}
void waiting_complete(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("complete waiting thread");
}
Ответ 7
не совсем уверен, что вы ожидаете. Вы имеете в виду, что вы хотите что-то сделать (BW), после чего вы хотите сделать что-то еще?
Используйте bw.RunWorkerCompleted, как и вы (используйте отдельную функцию для удобочитаемости), и в этой функции обратного вызова вы делаете следующее.
Запустите таймер, чтобы проверить, не работает ли работа слишком долго.
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
OnAsyncOperationCompleted(sender, args);
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
Timer Clock=new Timer();
Clock.Interval=1000;
Clock.Start();
Clock.Tick+=new EventHandler(Timer_Tick);
public void Timer_Tick(object sender,EventArgs eArgs)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
throw new TimedoutException("bla bla bla");
}
В OnDoWorkLoadChildren:
if ((worker.CancellationPending == true))
{
e.Cancel = true;
//return or something
}
Ответ 8
В OpenCV существует функция WaitKey. Ir позволяет решить эту проблему таким образом:
while (this->backgroundWorker1->IsBusy) {
waitKey(10);
std::cout << "Wait for background process: " << std::endl;
}
this->backgroundWorker1->RunWorkerAsync();
Ответ 9
Я также искал подходящее решение. Я решил ждать с помощью эксклюзивного замка. Критический путь в коде записывается в общий контейнер (здесь консоль) и увеличивается или уменьшается рабочих. Ни один поток не должен вмешиваться во время записи в эту переменную, иначе счет больше не гарантируется.
public class Program
{
public static int worker = 0;
public static object lockObject = 0;
static void Main(string[] args)
{
BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest();
backgroundworkerTest.WalkDir("C:\\");
while (backgroundworkerTest.Worker > 0)
{
// Exclusive write on console
lock (backgroundworkerTest.ExclusiveLock)
{
Console.CursorTop = 4; Console.CursorLeft = 1;
var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker);
Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth-consoleOut.Length));
}
}
}
}
public class BackgroundworkerTest
{
private int worker = 0;
public object ExclusiveLock = 0;
public int Worker
{
get { return this.worker; }
}
public void WalkDir(string dir)
{
// Exclusive write on console
lock (this.ExclusiveLock)
{
Console.CursorTop = 1; Console.CursorLeft = 1;
var consoleOut = string.Format("Directory={0}", dir);
Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth*3 - consoleOut.Length));
}
var currentDir = new System.IO.DirectoryInfo(dir);
DirectoryInfo[] directoryList = null;
try
{
directoryList = currentDir.GetDirectories();
}
catch (UnauthorizedAccessException unauthorizedAccessException)
{
// No access to this directory, so let leave
return;
}
foreach (var directoryInfo in directoryList)
{
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
// Make sure that this worker variable is not messed up
lock (this.ExclusiveLock)
{
worker--;
}
};
DirectoryInfo info = directoryInfo;
bw.DoWork += (sender, args) => this.WalkDir(info.FullName);
lock (this.ExclusiveLock)
{
// Make sure that this worker variable is not messed up
worker++;
}
bw.RunWorkerAsync();
}
}
}
Ответ 10
Я использовал Задачи с BackgroundWorker
Вы можете создать любое количество задач и добавить их в список задач.
Рабочий запускается при добавлении задачи, перезапускается, если задача добавляется во время рабочего IsBusy, и останавливается, когда задач больше нет.
Это позволит вам обновлять графический интерфейс асинхронно столько, сколько вам нужно, не замораживая его.
Это работает для меня.
// 'tasks' is simply List<Task> that includes events for adding objects
private ObservableCollection<Task> tasks = new ObservableCollection<Task>();
// this will asynchronously iterate through the list of tasks
private BackgroundWorker task_worker = new BackgroundWorker();
public Form1()
{
InitializeComponent();
// set up the event handlers
tasks.CollectionChanged += tasks_CollectionChanged;
task_worker.DoWork += task_worker_DoWork;
task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted;
task_worker.WorkerSupportsCancellation = true;
}
// ----------- worker events
void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (tasks.Count != 0)
{
task_worker.RunWorkerAsync();
}
}
void task_worker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
foreach (Task t in tasks)
{
t.RunSynchronously();
tasks.Remove(t);
}
}
catch
{
task_worker.CancelAsync();
}
}
// ------------- task event
// runs when a task is added to the list
void tasks_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (!task_worker.IsBusy)
{
task_worker.RunWorkerAsync();
}
}
Теперь вам нужно создать новую задачу и добавить ее в список < > . Он будет выполняться рабочим в том порядке, в котором он был помещен в список < >
Task t = new Task(() => {
// do something here
});
tasks.Add(t);