【发布时间】:2011-11-18 09:17:07
【问题描述】:
在报告长时间运行的服务器操作的进度时,我遇到了一个奇怪的问题。 该应用程序具有客户端/服务器架构并用 C# 编写。客户端使用 WPF。
在客户端,我创建进度窗口并在后台工作人员中启动一个长时间运行的操作。此操作是通过远程调用调用的服务器方法。作为参数,服务器方法接受用于报告进度的特殊 ProgressContext 对象(参见下面的代码)。
一旦服务器开始执行一些使用 CPU/内存的繁重操作 - 进度窗口就会冻结。它不响应任何交互并且不更新进度。一段时间后,当繁重的操作完成后 - 进度窗口又回来了,就像什么都没发生一样。
看起来当我将后台工作人员的实例传递给服务器并且服务器线程负载很重时 - 它与锁定窗口后台工作人员的方式有关。如果我在没有远程调用的情况下使用相同的进度窗口 - 问题就会消失。
为了报告进度,我使用带有后台工作程序的进度窗口,就像网络上的许多示例一样。 这是进度窗口的 C# 代码:
public partial class ProgressWindow : Window
{
#region Fields
public static readonly DependencyProperty AutoIncrementProperty =
DependencyProperty.Register(
"AutoIncrement",
typeof(bool),
typeof(ProgressBar),
new UIPropertyMetadata(null));
private readonly BackgroundWorker m_worker;
private CultureInfo m_culture;
private bool m_isCancelled;
private Exception m_error = null;
private Action<IProgressContext> m_workerCallback;
#endregion
#region Constructors
/// <summary>
/// Inits the dialog without displaying it.
/// </summary>
public ProgressWindow()
{
InitializeComponent();
//init background worker
m_worker = new BackgroundWorker();
m_worker.WorkerReportsProgress = true;
m_worker.WorkerSupportsCancellation = true;
m_worker.DoWork += Worker_DoWork;
m_worker.ProgressChanged += Worker_ProgressChanged;
m_worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
AutoIncrement = true;
CancellingEnabled = false;
}
#endregion
#region Public Properties
public bool CancellingEnabled
{
get
{
return btnCancel.IsVisible;
}
set
{
btnCancel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}
}
public bool Cancelled
{
get
{
return m_isCancelled;
}
}
public bool AutoIncrement
{
get
{
return (bool)this.GetValue(AutoIncrementProperty);
}
set
{
this.SetValue(AutoIncrementProperty, value);
}
}
public Exception Error
{
get
{
return m_error;
}
}
#endregion
#region Public Methods
public void Run(Action<IProgressContext> action)
{
if (AutoIncrement)
{
progressBar.IsIndeterminate = true;
}
//store the UI culture
m_culture = CultureInfo.CurrentUICulture;
//store reference to callback handler and launch worker thread
m_workerCallback = action;
m_worker.RunWorkerAsync();
//display modal dialog (blocks caller)
ShowDialog();
}
#endregion
#region Private Methods
#region Event Handlers
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
//make sure the UI culture is properly set on the worker thread
Thread.CurrentThread.CurrentUICulture = m_culture;
ProgressContext context = new ProgressContext((BackgroundWorker)sender);
//invoke the callback method with the designated argument
m_workerCallback(context);
}
catch (Exception)
{
//disable cancelling and rethrow the exception
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(SendOrPostCallback)delegate { btnCancel.SetValue(Button.IsEnabledProperty, false); },
null);
throw;
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
btnCancel.IsEnabled = false;
m_worker.CancelAsync();
m_isCancelled = true;
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage != int.MinValue)
{
progressBar.Value = e.ProgressPercentage;
}
if (e.UserState != null)
{
lblStatus.Text = (string)e.UserState;
}
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
m_error = e.Error;
}
//update UI in case closing the dialog takes a moment
btnCancel.IsEnabled = false;
Close();
}
#endregion
#endregion
}
public class ProgressContext : MarshalByRefObject, IProgressContext
{
#region Fields
private BackgroundWorker m_worker;
#endregion
#region Constructors
public ProgressContext(BackgroundWorker worker)
{
m_worker = worker;
}
#endregion
#region Public Properties
public void ReportProgress(string message)
{
m_worker.ReportProgress(int.MinValue, message);
}
public void ReportProgress(int progress, string message)
{
m_worker.ReportProgress(progress, message);
}
public void ReportProgress(int progress)
{
m_worker.ReportProgress(progress);
}
public bool IsCancelled
{
get
{
return m_worker.CancellationPending;
}
}
#endregion
}
任何帮助将不胜感激。提前致谢。
【问题讨论】:
-
需要更多关于此处使用的远程处理的详细信息(可能还有代码)。
-
尝试使用
Dispatcher类和DispatcherPriority.Background设置progressBar值。 -
不幸的是,我无法提供执行的服务器端操作的详细代码,因为它们需要数千行。远程设置没有什么特别之处,在所有情况下都能正常工作。问题不会立即发生。报告进度,然后过一段时间进行繁重的操作 - UI 卡住了。如果有什么东西会导致这种行为,我很乐意检查。谢谢。
-
我尝试过使用 Dispatcher.Invoke 或 Dispatcher.BeginInvoke 以及设置优先级。这无济于事。有趣的是,当使用结果 Dispatcher.BeginInvoke (DispatcherOperation) 来检查状态时 - 当 UI 冻结时说 Pending。
-
还有一件事。没关系如果我设置进度条的值或将其保留为 IsIndeterminate - 没关系。通过更改标签的文本来报告进度就足够了。 UI 会在一段时间后冻结。
标签: c# .net wpf backgroundworker