4

私は、WinForms C# アプリケーションの別のスレッドで、ProgressBar (マーキー) を制御するバックグラウンド ワーカーを開始しようとしています。問題は、バーを表示に設定しようとしても何も起こらないことです。多くの形式の Invoke を試しましたが、役に立たないようです。

次のメソッドprogressBarCycleは、別のスレッドから呼び出されます。

    BackgroundWorker backgroundWorker = new BackgroundWorker();

    public void progressBarCycle(int duration)
    {
        backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.RunWorkerAsync(duration);
    }

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        worker.ReportProgress(0);

        DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument);
        while (DateTime.Now <= end)
        {
            System.Threading.Thread.Sleep(1000);
        }
    }

    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = false;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false));
        //        else progressBar1.Visible = false;
    }

    private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (!this.IsHandleCreated)
            this.CreateHandle();
        statusStrip1.Invoke((MethodInvoker)delegate
        {
            progressBar1.Visible = true;
        });
        //    if (!this.IsHandleCreated)
        //    {
        //        this.CreateHandle();
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
        //    }
        //    else
        //        if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true));
        //        else progressBar1.Visible = true;
    }

ここで明らかな何かが欠けていますか?コメント セクションは、私が試した他のものです。

4

3 に答える 3

6

ProgressChangedUI スレッドで (sync-context を介して) 既に発生しています。あなたProgressChangedはそれをする必要はありませんInvoke-UIを直接操作できます(対照的に、それDoWorkは絶対にできません)。worker.ReportProgress(...) おそらく本当の問題は、ループ内で何もしないことです。つまり、最初に 1 回しか発生しません。

完全な例を次に示します。

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        using (var worker = new BackgroundWorker {
            WorkerReportsProgress = true })
        using (var progBar = new ProgressBar {
            Visible = false, Step = 1, Maximum = 100,
            Dock = DockStyle.Bottom })
        using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" })
        using (var form = new Form { Controls = { btn, progBar } })
        {
            worker.ProgressChanged += (s,a) => {
                progBar.Visible = true;
                progBar.Value = a.ProgressPercentage;
            };
            worker.RunWorkerCompleted += delegate
            {
                progBar.Visible = false;
            };
            worker.DoWork += delegate
            {
                for (int i = 0; i < 100; i++)
                {
                    worker.ReportProgress(i);
                    Thread.Sleep(100);
                }
            };
            btn.Click += delegate
            {
                worker.RunWorkerAsync();
            };
            Application.Run(form);
        }
    }
}
于 2012-12-31T13:38:15.760 に答える
2

注意すべきもう1つの可能性は、進行状況バーがUIスレッドで実行されていることです。プログレスバーを表示し、それ自体を再描画して新しいプログレス量を表示するには、UIスレッドが自由に実行され、メインアプリケーションループでWindowsメッセージを処理する必要があります。

したがって、バックグラウンドワーカースレッドを開始した後、UIスレッドがビジー待機ループ内にあり、完了するのを待っているか、オフになって他の多くの作業を実行すると、Windowsメッセージは処理されず、プログレスバーは次のようになります。 「無反応」。この更新が引き続き行われるように、UIスレッドを解放する必要があります(つまり、イベントハンドラーから戻り、UIを通常どおり実行し続けることができます)。

これの危険性は、UIがアクティブな場合でも、ユーザーがUIを操作できることです。したがって、バックグラウンドワーカーがアクティブになったことを認識し、状況を適切に処理するようにUIを作成する必要があります(問題には、ユーザーがバックグラウンドワーカーを既に実行しているときに再開できるようにする、UIがワーカー中に情報を表示しようとするなどがあります。スレッドはビジー状態で更新しており、ユーザーは新しいドキュメントをロードするか、バックグラウンドワーカーがビジー状態のときに終了するかなどを決定しています。これに対する2つの主な解決策は、バックグラウンド作業の実行中に危険なものが開始されるのを防ぐ保護シールドでUIのすべてのビットをラップすることです(この方法でラップするコントロールがたくさんある場合、これは多くの作業になる可能性があります。バグをすり抜けてしまうような間違いを犯したり、UIを「保護されていない」ままにしておくのは簡単です。

于 2012-12-31T20:24:06.837 に答える
2
  1. progressBarCycleUI スレッドから実行します。RunWorkerAsyncが新しいスレッドを作成します。
  2. backgroundWorker_ProgressChanged単に呼び出し progressBar1.Visible = true;ます。の必要はありませんInvoke
  3. も追加することをお勧めしprogressBar1.Refresh();ます。
于 2012-12-31T18:48:25.090 に答える