C# 线程和 Windows 窗体

标签 c# multithreading concurrency

我对具有后台进程的响应式 GUI 的方法是否正确?如果没有,请批评并提供改进。特别是指出哪些代码可能会遇到死锁或竞争条件。

工作线程需要能够被取消并报告它的进度。我没有使用 BackgroundWorker,因为我见过的所有示例都在 Form 本身上有 Process 代码,而不是一个单独的对象。我考虑过为 BackgroundWorker 继承 LongRunningProcess,但我认为这会在对象上引入不必要的方法。理想情况下,我不希望有一个 Form 引用流程(“_lrp”),但我不知道如何取消流程,除非我在 LRP 上有一个检查标志的事件在调用者上,但这似乎不必要地复杂,甚至可能是错误的。

Windows 窗体(编辑:将 *.EndInvoke 调用移动到回调)

public partial class MainForm : Form
{
    MethodInvoker _startInvoker = null;
    MethodInvoker _stopInvoker = null;
    bool _started = false;

    LongRunningProcess _lrp = null;

    private void btnAction_Click(object sender, EventArgs e)
    {
        // This button acts as a Start/Stop switch.
        // GUI handling (changing button text etc) omitted
        if (!_started)
        {
            _started = true;
            var lrp = new LongRunningProcess();

            _startInvoker = new MethodInvoker((Action)(() => Start(lrp)));
            _startInvoker.BeginInvoke(new AsyncCallback(TransferEnded), null);
        }
        else
        {
            _started = false;
            _stopInvoker = new MethodInvoker(Stop);
                _stopInvoker.BeginInvoke(Stopped, null);
        }
    }

    private void Start(LongRunningProcess lrp)
    {
        // Store a reference to the process
        _lrp = lrp;

        // This is the same technique used by BackgroundWorker
        // The long running process calls this event when it 
        // reports its progress
        _lrp.ProgressChanged += new ProgressChangedEventHandler(_lrp_ProgressChanged);
        _lrp.RunProcess();
    }

    private void Stop()
    {
        // When this flag is set, the LRP will stop processing
        _lrp.CancellationPending = true;
    }

    // This method is called when the process completes
    private void TransferEnded(IAsyncResult asyncResult)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new Action<IAsyncResult>(TransferEnded), asyncResult);
        }
        else
        {
            _startInvoker.EndInvoke(asyncResult);
            _started = false;
            _lrp = null;
        }
    }

    private void Stopped(IAsyncResult asyncResult)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new Action<IAsyncResult>(Stopped), asyncResult);
        }
        else
        {
            _stopInvoker.EndInvoke(asyncResult);
            _lrp = null;
        }
    }

    private void _lrp_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // Update the progress
        // if (progressBar.InvokeRequired) etc...
    }
}

后台进程:

public class LongRunningProcess
{
    SendOrPostCallback _progressReporter;
    private readonly object _syncObject = new object();
    private bool _cancellationPending = false;

    public event ProgressChangedEventHandler ProgressChanged;

    public bool CancellationPending
    {
        get { lock (_syncObject) { return _cancellationPending; } }
        set { lock (_syncObject) { _cancellationPending = value; } }
    }

    private void ReportProgress(int percentProgress)
    {
        this._progressReporter(new ProgressChangedEventArgs(percentProgress, null));
    }

    private void ProgressReporter(object arg)
    {
        this.OnProgressChanged((ProgressChangedEventArgs)arg);
    }

    protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
    {
        if (ProgressChanged != null)
            ProgressChanged(this, e);
    }

    public bool RunProcess(string data)
    {
        // This code should be in the constructor
        _progressReporter = new SendOrPostCallback(this.ProgressReporter);

        for (int i = 0; i < LARGE_NUMBER; ++i)
        {
            if (this.CancellationPending)
                break;

            // Do work....
            // ...
            // ...

            // Update progress
            this.ReportProgress(percentageComplete);

            // Allow other threads to run
            Thread.Sleep(0)
        }

        return true;
    }
}

最佳答案

我喜欢把后台进程分离在一个单独的对象中。但是,我的印象是您的 UI 线程在后台进程完成之前一直处于阻塞状态,因为您在同一个按钮处理程序中调用了 BeginInvoke 和 EndInvoke。

MethodInvoker methodInvoker = new MethodInvoker((Action)(() => Start(lrp)));
IAsyncResult result = methodInvoker.BeginInvoke(new AsyncCallback(TransferEnded), null);
methodInvoker.EndInvoke(result);

还是我遗漏了什么?

关于C# 线程和 Windows 窗体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/986384/

相关文章:

java - 有关解释死锁的 oracle.com 并发代码的问题

c# - JSON.Net:反序列化多态类型而不指定程序集

c# - 在单元测试中确定线程安全

C# 如何更新 BlockingCollection<T> 中的元素?

java - 可重入同步——被调用同步方法的解锁

c++ - 谁能解释一下((a == 1 && a == 2 && a == 3)== true)?

java - 线程间的并发问题

c# - 应用程序 [Win32Exception (0x80004005) : The system cannot find the file specified] with model first EF

c# - 如何将 JSON 响应映射到自定义类对象

c# - 动态 LINQ,Select 函数,适用于 Enumerable,但不适用于 Queryable