c# - 报告客户端/服务器环境中的进度

标签 c# .net wpf backgroundworker

在报告长时间运行的服务器操作的进度时,我遇到了一个奇怪的问题。 该应用程序具有客户端/服务器架构并用 C# 编写。客户使用 WPF。

在客户端,我创建进度窗口并在后台工作人员中启动一个长时间运行的操作。此操作是通过远程处理调用的服务器方法。作为参数,服务器方法接受用于报告进度的特殊 ProgressContext 对象(请参见下面的代码)。

一旦服务器开始执行一些使用 CPU/内存的繁重操作 - 进度窗口就会卡住。它不响应任何交互,也不更新进度。一段时间后,当繁重的操作完成后 - 进度窗口恢复正常,就像什么也没发生一样。

看起来当我将后台 worker 的实例传递给服务器并且服务器线程负载很重时 - 它与锁定窗口后台 worker 的方式有关。如果我在没有远程调用的情况下使用相同的进度窗口 - 问题就会消失。

为了报告进度,我将进度窗口与 backgroundworker 一起使用,就像网络上的许多示例一样。 这是进度窗口的 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
}

任何帮助将不胜感激。提前致谢。

最佳答案

我怀疑 Backgroundworker 不适合以这种方式使用远程处理进行编码。

将 Backgroundworker 留在客户端,不要传递它并设置一个事件接收器,它是一个 MarshalByRefObject,它保留在客户端并从服务器调用/发出信号。

sink 反过来可以调用 Backgroundworker 上的方法。

关于c# - 报告客户端/服务器环境中的进度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8180138/

相关文章:

.net - nUnit 是否内置了模拟框架,我应该使用它吗?

c# - 具有附加属性的 ControlTemplate 中的命令绑定(bind)

c# - 顶部垂直对齐的多行文本框

wpf - 从 DataTrigger 内部更改样式

C# 使用内部属性 setter 模拟类

c# - 如何将 Collection ViewController 添加到 ViewController

c# - 如何以编程方式将 System.Windows.Controls.Parameter 绑定(bind)到 Silverlight 中的 ComboBox 的 SelectedItem

c# - 如何确定一个字符串是否包含来自另一个字符串的单词

c# - 按键 + 列表中的值对 Dictionary<int, List<int>> 进行排序

.net - 实例化时找不到数据库对象实例化