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

时间:2011-11-18 09:17:08

标签: c# .net wpf backgroundworker

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

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

一旦服务器开始执行一些利用CPU /内存的繁重操作,进度窗口就会冻结。它没有响应任何交互,也没有更新进度。经过一段时间的繁重操作 - 进度窗口恢复生机,就像没有发生任何事情一样。

当我将后台工作程序的实例传递给服务器并且服务器线程负载很重时,它看起来就像是窗口后台工作者的关联方式。如果我使用相同的进度窗口而没有远程调用 - 问题消失。

要报告进度,我会在网络上的许多示例中使用带有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
}

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

2 个答案:

答案 0 :(得分:0)

我怀疑Backgroundworker不适合以这种方式使用远程编组。

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

接收器可以调用Backgroundworker上的方法。

答案 1 :(得分:0)

感谢大家的投入。

问题的原因是另一个进程,它在不同的线程中通过自己的Dispatcher.Invoke访问服务器方法并导致锁定。这个过程初创公司很少见 - 因此它给人留下了一段时间后锁定的印象。

我可以给出的总体建议是使Dispatcher.Invoke / BeginInvoke方法尽可能轻,而不需要进行任何繁重的计算。事先做好您的服务器工作,并仅使用它们来更新UI。