首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >CancellationTokenSource需要建议

CancellationTokenSource需要建议
EN

Stack Overflow用户
提问于 2015-12-06 09:37:22
回答 1查看 459关注 0票数 2

我有一个这样的阶级工人:

代码语言:javascript
复制
public class Worker
{
    private List<SomeObject> _someObjectList = null;

    public Worker(SomeObject someObjectList)
    {
        _someObjectList = someObjectList;
    }

    public void Run(CancellationToken cancellationToken)
    {
        // Some time-consuming operation here
        foreach(var elem in _someObjectList)
        {
            cancellationToken.ThrowIfCancellationRequested();
            elem.DoSomethingLong();
        }
    }
}

我用的表格是:

代码语言:javascript
复制
public partial class SomeForm : Form
{
    private Worker _worker = null;

    public SomeForm(Worker worker)
    {
        InitializeComponent();

        _worker = worker;
    }

    async void RunButtonClick(object sender, EventArgs e)
    {
        // I have a way to cancel worker from MyForm but 
        //  I would like to be able to cancel it directly from Worker 
        //   so object would be intuitive.
        var tokenSource = new CancellationTokenSource();
        var task = Task.Factory.StartNew(() => _worker.Run(tokenSource.Token), tokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
        await task;     
    }
}

我需要一个更明确的解决方案来取消这个工人。如下所示:

代码语言:javascript
复制
_worker.Cancel();

我也想暂停一下,恢复这样的工人:

代码语言:javascript
复制
_worker.Pause();
_worker.Resume();

由于我实例化了CancellationTokenSource外部Worker类,所以看不到实现自己的Cancel方法的方法。我已经用这样的CancellationToken实现了暂停和恢复(我觉得这是一个非常糟糕的主意,但它有效):

代码语言:javascript
复制
public class Worker
{
    private List<SomeObject> _someObjectList = null;
    private CancellationTokenSource _pauseToken = null;

    public bool Paused { get; private set; }

    public Worker(SomeObject someObjectList)
    {
        _someObjectList = someObjectList;
    }

    public void Run(CancellationToken cancellationToken)
    {
        // Some time-consuming operation here
        foreach(var elem in _someObjectList)
        {
            if(Paused)
                _pauseToken.Token.WaitHandle.WaitOne(Timeout.Infinite);

            cancellationToken.ThrowIfCancellationRequested();
            elem.DoSomethingLong();
        }
    }

    public void Pause()
    {
        if(!Paused)
        {
            // For pausing and resuming...
            _pauseToken = new CancellationTokenSource();

            Paused = true;

        }
    }

    public void Resume()
    {
        if(Paused && _pauseToken != null)
        {
            _pauseToken.Cancel();

            Paused = false;
        }
    }
}

我需要建议如何实施取消方法和恢复/暂停方法最适当的方式。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-12-07 04:08:42

如果要在Worker类中封装取消行为,那么显然必须在适当的时间在适当的对象上调用CancellationTokenSource.Cancel()的某种机制。有两种明显的方法可以实现这一点:

  1. CancellationTokenSource对象嵌入到Worker类本身中。
  2. 提供一个回调机制,在调用Worker.Cancel()方法时,它将实际操作委托给其他类。

对于我来说,第二种方法似乎是任意复杂的,而第一种方法似乎很适合您希望自己公开一个Cancel()方法的类。

至于暂停和恢复,我同意下面的评论,你的问题,使用CancellationTokenSource是滥用这类目的。也就是说,在现代C#代码中,没有理由使用像ManualResetEvent这样的东西以及伴随而来的家政代码。

相反,您可以将Run()方法实现为async,并在TaskCompletionSource可用时使其具有await。然后它将有公共方法Pause()Resume(),其中Pause()方法将创建TaskCompletionSource对象,而Resume()方法将在该对象上设置结果。

这样做,您可以正常地实现您的Run()方法,而无需编写任何额外的维护代码来允许该方法暂停和恢复。编译器将为您生成所有这些代码,使用await语句作为方法暂停时可能返回的位置,然后在稍后继续执行。

另一种方法是自己编写所有的维护代码(很容易出错),或者甚至不从Run()方法返回,而只是阻塞线程直到操作恢复(在等待用户释放时不必要地绑定线程)。

如果您只有一个任务和一个非常简单的用户场景,这可能是过火了。阻塞线程可能就足够了。但是,如果您有更复杂的场景,这种方法将完全支持这些方案,同时仍然提供对线程池的最有效的使用。

下面是一个简短的代码示例,演示了我前面描述的技术:

Worker.cs

代码语言:javascript
复制
class Worker
{
    private static readonly TimeSpan _ktotalDuration = TimeSpan.FromSeconds(5);
    private const int _kintervalCount = 20;

    public bool IsPaused { get { return _pauseCompletionSource != null; } }
    public event EventHandler IsPausedChanged;

    private readonly object _lock = new object();
    private CancellationTokenSource _cancelSource;
    private volatile TaskCompletionSource<object> _pauseCompletionSource;

    public async Task Run(IProgress<int> progress)
    {
        _cancelSource = new CancellationTokenSource();

        TimeSpan sleepDuration = TimeSpan.FromTicks(_ktotalDuration.Ticks / _kintervalCount);

        for (int i = 0; i < 100; i += (100 / _kintervalCount))
        {
            progress.Report(i);
            Thread.Sleep(sleepDuration);
            _cancelSource.Token.ThrowIfCancellationRequested();

            TaskCompletionSource<object> pauseCompletionSource;

            lock (_lock)
            {
                pauseCompletionSource = _pauseCompletionSource;
            }

            if (pauseCompletionSource != null)
            {
                RaiseEvent(IsPausedChanged);

                try
                {
                    await pauseCompletionSource.Task;
                }
                finally
                {
                    lock (_lock)
                    {
                        _pauseCompletionSource = null;
                    }
                    RaiseEvent(IsPausedChanged);
                }
            }
        }

        progress.Report(100);

        lock (_lock)
        {
            _cancelSource.Dispose();
            _cancelSource = null;

            // Just in case pausing lost the race with cancelling or finishing
            _pauseCompletionSource = null;
        }
    }

    public void Cancel()
    {
        lock (_lock)
        {
            if (_cancelSource != null)
            {
                if (_pauseCompletionSource == null)
                {
                    _cancelSource.Cancel();
                }
                else
                {
                    _pauseCompletionSource.SetCanceled();
                }
            }
        }
    }

    public void Pause()
    {
        lock (_lock)
        {
            if (_pauseCompletionSource == null)
            {
                _pauseCompletionSource = new TaskCompletionSource<object>();
            }
        }
    }

    public void Resume()
    {
        lock (_lock)
        {
            if (_pauseCompletionSource != null)
            {
                _pauseCompletionSource.SetResult(null);
            }
        }
    }

    private void RaiseEvent(EventHandler handler)
    {
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

Form1.cs

代码语言:javascript
复制
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private Worker _worker;

    private async void button1_Click(object sender, EventArgs e)
    {
        Progress<int> progress = new Progress<int>(i => progressBar1.Value = i);
        _worker = new Worker();

        _worker.IsPausedChanged += (sender1, e1) =>
        {
            Invoke((Action)(() =>
            {
                button3.Enabled = !_worker.IsPaused;
                button4.Enabled = _worker.IsPaused;
            }));
        };

        button1.Enabled = false;
        button2.Enabled = button3.Enabled = true;

        try
        {
            await Task.Run(() => _worker.Run(progress));

            // let the progress bar catch up before we clear it
            await Task.Delay(1000);
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Operation was cancelled");
        }

        progressBar1.Value = 0;
        button2.Enabled = button3.Enabled = button4.Enabled = false;
        button1.Enabled = true;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        _worker.Cancel();
    }

    private void button3_Click(object sender, EventArgs e)
    {
        _worker.Pause();
    }

    private void button4_Click(object sender, EventArgs e)
    {
        _worker.Resume();
    }
}

Form1.Designer.cs

代码语言:javascript
复制
partial class Form1
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.progressBar1 = new System.Windows.Forms.ProgressBar();
        this.button1 = new System.Windows.Forms.Button();
        this.button2 = new System.Windows.Forms.Button();
        this.button3 = new System.Windows.Forms.Button();
        this.button4 = new System.Windows.Forms.Button();
        this.SuspendLayout();
        // 
        // progressBar1
        // 
        this.progressBar1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
        | System.Windows.Forms.AnchorStyles.Right)));
        this.progressBar1.Location = new System.Drawing.Point(12, 42);
        this.progressBar1.Name = "progressBar1";
        this.progressBar1.Size = new System.Drawing.Size(427, 23);
        this.progressBar1.TabIndex = 0;
        // 
        // button1
        // 
        this.button1.Location = new System.Drawing.Point(13, 13);
        this.button1.Name = "button1";
        this.button1.Size = new System.Drawing.Size(75, 23);
        this.button1.TabIndex = 1;
        this.button1.Text = "Start";
        this.button1.UseVisualStyleBackColor = true;
        this.button1.Click += new System.EventHandler(this.button1_Click);
        // 
        // button2
        // 
        this.button2.Enabled = false;
        this.button2.Location = new System.Drawing.Point(94, 13);
        this.button2.Name = "button2";
        this.button2.Size = new System.Drawing.Size(75, 23);
        this.button2.TabIndex = 2;
        this.button2.Text = "Cancel";
        this.button2.UseVisualStyleBackColor = true;
        this.button2.Click += new System.EventHandler(this.button2_Click);
        // 
        // button3
        // 
        this.button3.Enabled = false;
        this.button3.Location = new System.Drawing.Point(175, 13);
        this.button3.Name = "button3";
        this.button3.Size = new System.Drawing.Size(75, 23);
        this.button3.TabIndex = 3;
        this.button3.Text = "Pause";
        this.button3.UseVisualStyleBackColor = true;
        this.button3.Click += new System.EventHandler(this.button3_Click);
        // 
        // button4
        // 
        this.button4.Enabled = false;
        this.button4.Location = new System.Drawing.Point(256, 13);
        this.button4.Name = "button4";
        this.button4.Size = new System.Drawing.Size(75, 23);
        this.button4.TabIndex = 4;
        this.button4.Text = "Resume";
        this.button4.UseVisualStyleBackColor = true;
        this.button4.Click += new System.EventHandler(this.button4_Click);
        // 
        // Form1
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(451, 287);
        this.Controls.Add(this.button4);
        this.Controls.Add(this.button3);
        this.Controls.Add(this.button2);
        this.Controls.Add(this.button1);
        this.Controls.Add(this.progressBar1);
        this.Name = "Form1";
        this.Text = "Form1";
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.ProgressBar progressBar1;
    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Button button2;
    private System.Windows.Forms.Button button3;
    private System.Windows.Forms.Button button4;
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34115965

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档