【问题标题】:Report progress in client/server environment在客户端/服务器环境中报告进度
【发布时间】: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


【解决方案1】:

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

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

sink 可以调用 Backgroundworker 上的方法。

【讨论】:

  • 实际上,如果我说对了,这正是我现在所拥有的 - ProgressContext 类是 MarshalByRef。它是穿过远程边界的那个。当从服务器端调用它的方法时 - 在客户端它调用它的 m_worker 对应方法(参见原始消息中发布的代码)。或者你有不同的想法?
  • 啊,是的,现在我理解了你的代码。您是否将回调标记为 OneWay?
  • 远程处理部分不是用 WCF 编写的。这是普通的旧远程处理。
  • 查找:System.Runtime.Remoting.Messaging.OneWay :)
  • 从未使用过此属性。谢谢(你的)信息。虽然这不影响问题。
【解决方案2】:

感谢大家的意见。

问题的原因是不同线程中的另一个进程通过其自己的 Dispatcher.Invoke 访问服务器方法并导致锁定。这个过程的启动很少见——因此它给人一种在一段时间后锁定的印象。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 1970-01-01
    • 2011-03-18
    • 1970-01-01
    • 2017-05-28
    • 2011-09-02
    相关资源
    最近更新 更多