C # Threading and Windows Forms

Is my approach to a flexible GUI with a background process correct? If not, please, please criticize and suggest improvements. In particular, indicate which code could potentially suffer from a deadlock or race condition.

Workflow should be canceled and report on its progress. I did not use BackgroundWorker, because all the examples that I saw have the process code in the form itself, and not a separate object. I was thinking about inheriting LongRunningProcess for BackgroundWorker, but I decided that I would introduce unnecessary methods for the object. Ideally, I would prefer not to have a form link for the process ("_lrp"), but I don’t see how I could cancel this process if I don’t have an event in LRP that checks the flag on the caller, but it seems overly complicated and maybe even wrong.

Windows Form (change: moved * .EndInvoke calls a callback)

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...
    }
}

Background process:

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;
    }
}
+3
source share
5 answers

. , , , BeginInvoke EndInvoke .

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

- ?

+1

MethodInvoker.BeginInvoke(). , Thread.Start()...?

, , EndInvoke , BeginInvoke. , - EndInvoke . , , - - , . , , LRP .

, BeginInvoke, LRP . , , . . , . Thread, .

, , LRP , ManualResetEvent . , .

+1

_cancellationPending . Stop ?

, :

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

, ;)

+1

, OnProgressChanged, , . .

private static object eventSyncLock = new object();

protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
    ProgressChangedEventHandler handler;
    lock(eventSyncLock)
    {
      handler = ProgressChanged;
    }
    if (handler != null)
        handler(this, e);
}
0

BackgroundWorker - Form. , . Work BackgroundWorker , null .

BackgroundWorker, ProgressReporting, (BackgroundWorker bgWorker, params object [] otherParams) , :

    if( bgWorker != null && bgWorker.WorkerReportsProgress )
    {
        bgWorker.ReportProgress( percentage );
    }

... CancellationPending.

Forms . bgWorker.DoWork += new DoWorkEventHandler( startBgWorker );, Worker.Work, bgWorker .

, bgWorker.RunWorkerAsync.

bgWorker.CancelAsync, , CancellationPending.

RunWorkerCompleted, , . , , .

Work, , Forms ComponentModel.

And, of course, you implement the progresschanged event without having to reinvent the wheel on it. ProTip: ProgressChangedEventArgs takes an int, but does not force it to be no more than 100. To report a larger percentage of progress, pass an argument with a multiplier (say 100), so 14.32% will be Progress 1432. Then you can format the display or override the progress bar or show its like a text box. (all with a dry design)

0
source

Source: https://habr.com/ru/post/1710332/


All Articles