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

发布于 2024-12-16 21:11:05 字数 5351 浏览 0 评论 0原文

在报告长时间运行的服务器操作的进度时,我遇到了一个奇怪的问题。 该应用程序具有客户端/服务器架构并用 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
}

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

I have a strange problem when reporting progress of the long running server operation.
The application has client/server architecture and written in C#. Client uses WPF.

On client side I create progress window and start in background worker a long running operation. This operation is a server method called via remoting. As argument server method accepts special ProgressContext object that is used to report progress (see code below).

As soon as server starts performing some heavy operations that utilize CPU/Memory - the progress window becomes frozen. Its not responding to any interactions and do not update progress. After a while when heavy operations are done - the progress window comes back to live like nothing happened.

It looks like when I pass instance of background worker to the server and server thread is heavy loaded - it some how locks the window backgroundworker is related to. If I use the same progress window without remoting calls - problem dissapears.

To report progress I use progress window with backgroundworker as in many samples around the web.
here is C# code for the progress window:

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
}

Any help will be appreciated. Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

千年*琉璃梦 2024-12-23 21:11:05

我怀疑后台工作人员不适合使用这种方式进行远程处理。

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

接收器又可以调用后台工作者上的方法。

I suspect the Backgroundworker is not fit for being marshaled using remoting this way.

Leave the Backgroundworker at the client, do not pass it and setup an event sink that is a MarshalByRefObject which remains on the client and is called/signaled from the server.

The sink in its turn can invoke methods on the Backgroundworker.

李不 2024-12-23 21:11:05

感谢大家的意见。

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

我可以给出的总体建议是使 Dispatcher.Invoke/BeginInvoke 方法尽可能轻,内部不进行任何繁重的计算。预先完成服务器工作并使用它们来更新 UI。

Thanks everyone for the input.

The reason for the problem was another process that in different thread was accessing server methods via its own Dispatcher.Invoke and causing locks. This process startups were rare - thus it made an impression of locking up after a while.

The overall recommendation I can give is to make Dispatcher.Invoke/BeginInvoke methods as light as possible without any heavy calculations inside. Do your server job beforehand and use them just to update the UI.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文