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());
};
}
我有一个带有大 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。我尝试使用 :
传递我的 UserControlprogressBarThread.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());
};
}