为什么BackgroundWorker总是忙碌?

12

我在我的WPF应用程序的后台工作器中意识到了一些奇怪的事情。

我现在想要完成的目标是等待BW结束后再开始另一个线程。

请检查以下代码:

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();
}
请注意,我添加了一个Dispatcher.Invoke来等待bw不忙,但如果我调用它并且RunWorkerAsyncCompleted事件从未触发,则一直处于忙碌状态。然而,如果我删除那行代码,它会触发事件,这真的很奇怪。
我该如何等待bw完成?

不清楚你删除了哪一行,做了什么更改...你能详细解释一下吗? - horgh
5个回答

31
这被称为“死锁”,是一个非常普遍的线程问题。BGW无法停止忙碌直到RunWorkerCompleted事件运行。该事件在您应用程序的主线程上运行,只有当您的主线程没有忙于做其他事情时才能运行。它必须处于空闲状态,在调度器循环内运行。
但是您的主线程不空闲,它卡在while()循环中等待IsBusy返回false。因此事件不能运行,因为主线程忙于等待BGW完成,而BGW无法完成,因为主线程从未变为空闲状态。"致命的拥抱",也称死锁。

您必须以不同的方式处理这个问题,不能等待。例如创建BGW的另一个实例或将等待后的代码移至RunWorkerCompleted事件处理程序中。如果通过按钮的Click事件激活它,请确保在调用RunWorkerAsync()时禁用按钮,在RunWorkerCompleted中重新启用它。

1
我有没有其他选项可以检查,直到我的BGW完成? - Darf Zon
3
另一个不太优美的解决方案是:如果等待循环在MainThread中,则在循环中调用“Application.DoEvents();”。这也将解决死锁问题。 - Ralph Erdt

6
死锁是由于应用程序的“主”线程无法运行(和处理事件),因此Backgroundworker无法触发其finished-function。要允许应用程序触发此操作,您可以添加Application.DoEvents();。 在类似情况下,我目前使用的就是这个方法,看起来非常好用!
    while (bw.IsBusy)
    {
        Application.DoEvents();
        System.Threading.Thread.Sleep(100);
    }

有没有 WPF 原生版本的 Application.DoEvents?该命名空间通常与 winforms 相关联,不会在 WPF 应用程序中正常包含。 - Denise Skidmore

3

我不确定我是否理解正确,但是我认为您正在尝试重新启动BackgroundWorker(所以如果它正在忙碌中,您需要先停止它然后再重新开始),如果这是您想要的,请尝试以下方法:

在您的类中声明此delegate

    private delegate bool StateChecker();

使用以下代码重新启动您的BackgroundWorker

    StateChecker stillWorking = () => { return bw.IsBusy; };

    if (bw.IsBusy)
    {
        bw.CancelAsync();
        while ((bool)this.Dispatcher.Invoke(stillWorking, null)) Thread.Sleep(15);
    }
    bw.RunWorkerAsync();

问题中的调度程序是什么? - sMuLe

2

这里是完整且可用的代码。

C#(Windows类)

 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

<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>

-1
将其放入另一个线程中:
private void myButton_Click(object sender, RoutedEventArgs e)
{
   BackgroundWorker mainWorker = new BackgroundWorker();
   mainWorker.DoWork += new DoWorkEventHandler(mainWorker_DoWork);
   mainWorker.RunWorkerAsync();
}

void mainWorker_DoWork(object sender, DoWorkEventArgs e)
{
   for(int x = 0; x >= 100; x++)
   {
      BackgroundWorker worker = new BackgroundWorker();
      worker.DoWork += new DoWorkEventHandler(worker_DoWork);
      worker.RunWorkerAsync(argument: x);
      while(worker.IsBusy)
      {
         Thread.Sleep(100);
      }
   }
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
   string iam = "Hello i'm thread number: " + e.Argument.ToString();
   //do something ...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接