C# WPF 从不同的线程取消 ProgressBar

C# WPF Cancel ProgressBar from a different Thread

我有一个带有大 table 的用户控件,它使用大量转换器来显示值。我正在尝试在一个新的 Window 中显示一个 ProgressBar,它具有 Indeterminate State,当 UserControl Loaded 事件被触发时,它会自动关闭。

这是在我的用户控件的后台代码中创建的线程:

Thread progressBarThread = new Thread(new ThreadStart(delegate { 
    ProgressBarWindow progressBarWindow = new ProgressBarWindow();
    progressBarWindow.IsIndeterminate = true;
    progressBarWindow.LaunchProgressBarInBackground();
    progressBarWindow.ShowDialog();
}));

progressBarThread.SetApartmentState(ApartmentState.STA);
progressBarThread.Start();

this.Loaded += (sender, e) => { Dispatcher.FromThread(progressBarThread).InvokeShutdown(); };

这段代码是 "working",它正在打开 progressBarWindow 但是当我使用 InvokeShutdown 关闭线程时(我同意这是最丑陋的做法)。问题是来自我的 backgroundWorker 的 DoWork。

这是 DoWork 函数:

private void BackgroundWorker_WaitUntilShouldStop(object sender, DoWorkEventArgs e)
{
    // Do not access the form's BackgroundWorker reference directly.
    // Instead, use the reference provided by the sender parameter.
    BackgroundWorker bw = sender as BackgroundWorker;

    // Start the time-consuming operation.
    while (!bw.CancellationPending)
    {
        Thread.Sleep(500);
    }
}

我想调用 ProgressBarWindow 中包含的函数来停止 DoWork 运行并正常关闭 ProgressBarWindow 使用:

progressBar.StopProgressBarInBackground();

此方法正在调用 backgroundWorker.CancelAsync();

这将导致 backgroundWorker 终止和 progressBarWindow 自动关闭。

但是我无法访问 progressBarThread 中的 progressBar。我尝试使用 :

传递我的 UserControl
progressBarThread.Start(this);

this 是主要的 window.

尝试从主线程传递变量时,抛出此错误:

An exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll but was not handled in user code  Additional information: The calling thread cannot access this object because a different thread owns it.

有人有不使用 myThread.InvokeShutdown() 的正确方法吗?

编辑 1 :

我找到了使用 volatile 变量解决我的问题的方法:

volatile bool _isLoaded;
void CreateAndStopProgressBarWhenIsLoaded()
{
    Thread progressBarThread= new Thread(new ThreadStart(    
    {
        Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
        progressBar.IsIndeterminate = true;
        progressBar.LaunchProgressBarInBackground();

        DispatcherTimer dispatcherTimer = new DispatcherTimer();
        dispatcherTimer = new DispatcherTimer();
        dispatcherTimer.Tick += (sender, e) => {
            if (_isLoaded)
                progressBar.StopProgressBarInBackground();
        };
        // Try to stop `progressBar` every 500 ms
        dispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
        dispatcherTimer.Start();

        progressBar.ShowDialog();

        // Will only be reached once progressBar.ShowDialog(); returns
        dispatcherTimer.Stop();
    }));
    progressBarThread.SetApartmentState(ApartmentState.STA);
    progressBarThread.Start();

    this.Loaded += (sender, e) => {
        _isLoaded = this.IsLoaded;
        progressBarThread.Join(); // Wait for progressBarThread to end
    };
}

现在的问题是你有更好的解决方案吗?

编辑 2 :

感谢@AlexSeleznyov,这是我的最终解决方案:

    void CreateAndStopProgressBarWhenIsLoaded()
    {
        Controls.ProgressBar.ProgressBar pb = null;
        ManualResetEvent manualResetEvent = new ManualResetEvent(false);

        Thread progressBarThread = new Thread(new ThreadStart(delegate
        {
            Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
            pb = progressBar;
            manualResetEvent.Set();
            progressBar.IsIndeterminate = true;
            progressBar.LaunchProgressBarInBackground();

            progressBar.ShowDialog();
        }));
        progressBarThread.SetApartmentState(ApartmentState.STA);
        progressBarThread.Start();

        this.Loaded += (sender, e) => {
            pb.Dispatcher.Invoke(delegate {
                manualResetEvent.WaitOne();
                pb.StopProgressBarInBackground();
            });
            progressBarThread.Join();
        };
    }

我认为您可以在 ProgressBarWindow 中使用 BackgroundWorker.RunWorkerCompleted - 它会在您取消 backgroundWorker 时调用。

private void backgroundWorker_RunWorkerCompleted(object sender, 
                                                 RunWorkerCompletedEventArgs e)
{
    if (e.Cancelled)
    {
        //close the window
    }
}

您可以尝试这种方法,缓存 ProgressBar 实例,然后从另一个线程使用它。 Dispatcher.Invoke 消除了对 CheckAccess 我在评论中提到的需求。

void CreateAndStopProgressBarWhenIsLoaded()
{
    var pb = new Controls.ProgressBar.ProgressBar[1];

    Thread progressBarThread= new Thread(new ThreadStart(    
    {
            Controls.ProgressBar.ProgressBar progressBar = new Controls.ProgressBar.ProgressBar();
            pb[0] = progressBar;
            progressBar.IsIndeterminate = true;
            progressBar.LaunchProgressBarInBackground();

            progressBar.ShowDialog();
    }));
    progressBarThread.SetApartmentState(ApartmentState.STA);
    progressBarThread.Start();

    this.Loaded += (sender, e) => {
         pb[0].Dispatcher.Invoke(()=>pb[0].Close());
    };

}