C# 为什么BackgroundWorker 总是很忙?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14430979/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why BackgroundWorker always is busy?
提问by Darf Zon
I realized something strange in my background worker in my WPF application.
我在 WPF 应用程序的后台工作人员中发现了一些奇怪的东西。
What I'm trying to accomplish right now is to wait until the BW finishes to start another thread.
我现在想要完成的是等到 BW 完成启动另一个线程。
Check the following code:
检查以下代码:
if (bw.IsBusy)
{
bw.CancelAsync();
System.Threading.ThreadStart WaitThread =
new System.Threading.ThreadStart(delegate() {
while (bw.IsBusy)
{
System.Threading.Thread.Sleep(100);
}
bw.RunWorkerAsync();
});
System.Windows.Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
WaitThread); // if I remove this line, bw fires RunWorkerAsyncEvent
}
else
{
bw.RunWorkerAsync();
}
Please note that I added a Dispatcher.Invoke to wait until the bw is not busy, but ALL THE TIME IS BUSY if I Invoke it and never fires the RunWorkerAsyncCompleted
event. Although,, if I remove that line, FIRES the event and it's really strange that.
请注意,我添加了一个 Dispatcher.Invoke 来等待 bw 不忙,但如果我调用它并且从不触发RunWorkerAsyncCompleted
事件,则所有时间都很忙。虽然,如果我删除那条线,会触发事件,这真的很奇怪。
How can I wait until the bw finishes?
我怎样才能等到 bw 完成?
回答by Hans Passant
This is called "deadlock", a very common threading problem. The BGW cannot stop being busy until the RunWorkerCompleted event runs. That event runs on your app's main thread, it can only run when your main thread isn't busy doing something else. It has to be idle, running inside the dispatcher loop.
这称为“死锁”,这是一个非常常见的线程问题。在 RunWorkerCompleted 事件运行之前,BGW 无法停止忙碌。该事件在您的应用程序的主线程上运行,它只能在您的主线程不忙于做其他事情时运行。它必须是空闲的,在调度程序循环内运行。
But your main thread isn't idle, it is stuck inside the while() loop waiting for IsBusy to return false. So the event can never run since the main thread is busy waiting for the BGW to complete, the BGW cannot complete because the main thread never goes idle. "Deadly embrace", aka deadlock.
但是您的主线程并没有空闲,它被困在 while() 循环中,等待 IsBusy 返回 false。因此事件永远不会运行,因为主线程忙于等待 BGW 完成,BGW 无法完成,因为主线程永远不会空闲。“致命拥抱”,又名僵局。
You will have to do this differently, you cannot wait. Say by creating another instance of BGW. Or moving the code after the wait into the RunWorkerCompleted event handler. If it is activated by, say, a button's Click event then be sure to disable the button when call RunWorkerAsync(), re-enable it in RunWorkerCompleted.
你将不得不以不同的方式做这件事,你不能等待。比如说创建另一个 BGW 实例。或者将等待后的代码移动到 RunWorkerCompleted 事件处理程序中。如果它是由按钮的 Click 事件激活的,那么请确保在调用 RunWorkerAsync() 时禁用该按钮,并在 RunWorkerCompleted 中重新启用它。
回答by someone else
I'm not sure if I understood correctly, but I think you're are trying to restart the BackgroundWorker(so if it's busy, you stop it first and then you start it again), if this is what you want, try this:
我不确定我的理解是否正确,但我认为您正在尝试重新启动BackgroundWorker(因此,如果它很忙,请先将其停止,然后再重新启动),如果这是您想要的,请尝试以下操作:
declare this delegatesomewhere in your class:
在您班级的某处声明此委托:
private delegate bool StateChecker();
and use this code to restart your BackgroundWorker:
并使用此代码重新启动您的BackgroundWorker:
StateChecker stillWorking = () => { return bw.IsBusy; };
if (bw.IsBusy)
{
bw.CancelAsync();
while ((bool)this.Dispatcher.Invoke(stillWorking, null)) Thread.Sleep(15);
}
bw.RunWorkerAsync();
回答by Developer
Here is full and working code.
这是完整的工作代码。
C# (window's class)
C#(窗口类)
public partial class ForceSyncWindow : Window
{
BackgroundWorker backgroundWorker = new BackgroundWorker();
public ForceSyncWindow()
{
InitializeComponent();
ProgressBar1.Visibility = System.Windows.Visibility.Hidden;
backgroundWorker.WorkerSupportsCancellation = true;
// To report progress from the background worker we need to set this property
backgroundWorker.WorkerReportsProgress = true;
// This event will be raised on the worker thread when the worker starts
backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
// This event will be raised when we call ReportProgress
backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
ProgressBar1.Value = 0;
// TODO LOG = "Canceled";
}
else
{
// Finally, handle the case where the operation
// succeeded.
// TODO LOG e.Result.ToString();
}
ProgressBar1.Value = 0;
ProgressBar1.Visibility = System.Windows.Visibility.Hidden;
// Enable the Synchronize button.
this.Synchronize.IsEnabled = true;
// Disable the Cancel button.
this.Cancel.IsEnabled = false;
}
// On worker thread so do our thing!
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Your background task goes here
for (int i = 0; i <= 100; i++)
{
if (backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
// Report progress to 'UI' thread
backgroundWorker.ReportProgress(i);
// Simulate long task
System.Threading.Thread.Sleep(100); ;
}
}
}
// Back on the 'UI' thread so we can update the progress bar
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// The progress percentage is a property of e
ProgressBar1.Value = e.ProgressPercentage;
}
private void Synchronize_Click(object sender, RoutedEventArgs e)
{
ProgressBar1.Value = 0;
ProgressBar1.Visibility = System.Windows.Visibility.Visible;
// Disable the Synchronize button.
this.Synchronize.IsEnabled = false;
// Enable the Cancel button.
this.Cancel.IsEnabled = true;
// Start the background worker
backgroundWorker.RunWorkerAsync();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (backgroundWorker.IsBusy)
{
// Cancel the background worker
backgroundWorker.CancelAsync();
}
}
}
XAML
XAML
<Window x:Class="MySyncManager.Views.ForceSyncWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ForceSyncWindow" Height="300" Width="509" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<Button Content="Synchronize" Name="Synchronize" HorizontalAlignment="Left" Margin="411,10,0,0" VerticalAlignment="Top" Width="75" Click="Synchronize_Click"/>
<RichTextBox HorizontalAlignment="Left" Height="132" Margin="10,116,0,0" VerticalAlignment="Top" Width="476">
</RichTextBox>
<Button Content="Cancel" x:Name="Cancel" HorizontalAlignment="Left" Margin="411,40,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.508,2.154" Click="Cancel_Click"/>
<ProgressBar Name="ProgressBar1" HorizontalAlignment="Left" Height="10" Margin="10,101,0,0" VerticalAlignment="Top" Width="476"/>
</Grid>
</Window>
回答by Gnutt
The deadlock occurs due to the "main" thread of the application can not run (and handle events), thus the Backgroundworker is never able to fire it's finished-function. What you can do to allow the application to fire this is add an Application.DoEvents();
由于应用程序的“主”线程无法运行(和处理事件),因此发生死锁,因此后台工作程序永远无法触发它的完成功能。您可以做的是允许应用程序触发它是添加一个 Application.DoEvents();
This is what I currently use in a similiar scenario, and it appears to work like a charm!
这是我目前在类似场景中使用的,它看起来很有魅力!
while (bw.IsBusy)
{
Application.DoEvents();
System.Threading.Thread.Sleep(100);
}