【问题标题】:Control and cancel multiple tasks控制和取消多个任务
【发布时间】:2016-01-03 03:10:25
【问题描述】:

我尝试编写简单的 WinForm 应用程序,但它的行为有些问题。 目的是在面板上用两种可选颜色(红色和黑色)绘制小矩形。有 4 个按钮 - 每种颜色对 - 画红色停止红色画黑色停止黑色

当点击Draw按钮时,会生成DrawRectangles的新任务。 当停止按钮被点击时,任务将被取消,使用 CancellationTokenSource。

问题是:当我创建了许多任务(即绘制红色的 3 个任务)之后,我按下停止按钮,它只取消了第一个任务,而我无法取消其他任务(绘制红色任务' ) 然后他们跑得没完没了。

以下代码:

public partial class Form1 : Form
{
    CancellationTokenSource cnDrawRedToken;
    CancellationTokenSource cnDrawBlackToken;
    public Form1()
    {
        InitializeComponent();          
    }

    private void Form1_Load(object sender, EventArgs e{}

    private void panel1_Paint(object sender, PaintEventArgs e){}

    private void RedBtn_Click(object sender, EventArgs e)
    {
        RunDrawingTask(SetSpecificPen(Color.Red),out this.cnDrawRedToken);           
    }

    private async Task DrawRectangles(int height, int width, Random random, Rectangle rectangle, Pen blackPen, CancellationToken cnToken)
    {
        while(true)
        {
            if (cnToken.IsCancellationRequested)
                return;
            rectangle.x = random.Next(0, width);
            rectangle.y = random.Next(0, height);
            this.panel1.CreateGraphics().DrawRectangle(blackPen, rectangle.x, rectangle.y, rectangle.width, rectangle.height);
            //Thread.Sleep(200);
            await Task.Delay(150);
        }
    }

    private Pen SetSpecificPen(Color color)
    {
        Pen blackPen = new Pen(color, 2);
        return blackPen;
    }

    private Rectangle InitRectangleWidthAndHeights()
    {
        Rectangle rectangle = new Rectangle();

        rectangle.width = 10;
        rectangle.height = 10;
        return rectangle;
    }

    private void StpRedBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawRedToken != null)
        {
            this.cnDrawRedToken.Cancel();
            this.cnDrawRedToken = null; 
        }
        else
        {
            this.cnDrawRedToken = new CancellationTokenSource();
        }
    }

    private void BlackBtn_Click(object sender, EventArgs e)
    {
        RunDrawingTask(SetSpecificPen(Color.Black),out this.cnDrawBlackToken);
    }

    private void StpBlkBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawBlackToken != null)
        {
            this.cnDrawBlackToken.Cancel();
            this.cnDrawBlackToken = null; 
        }
    }

    private void RunDrawingTask(Pen specificPen, out CancellationTokenSource cnTokenSource) 
    {
        int height = this.panel1.Height;
        int width = this.panel1.Width;
        Random random = new Random();
        Rectangle rectangle = InitRectangleWidthAndHeights();

        cnTokenSource = new CancellationTokenSource();
        CancellationTokenSource cts = cnTokenSource;
        Task t = Task.Factory.StartNew(() => DrawRectangles(height, width, random, rectangle, specificPen, cts.Token), cts.Token);
    }
}

我想要做的是当我点击停止按钮时,所有相同颜色的正在运行的任务都会被取消。

更新: 正如法比奥建议的那样,我重写了我的点击方法以及受影响的方法。之后,它可以按需要工作。

public Form1()
    {
        InitializeComponent();
        cnDrawRedToken = new CancellationTokenSource() ;
        cnDrawBlackToken = new CancellationTokenSource();
    }
 private void RedBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawRedToken.IsCancellationRequested)
        {
           this.cnDrawRedToken = null;
           this.cnDrawRedToken = new CancellationTokenSource();
        }           
        RunDrawingTask(SetSpecificPen(Color.Red), this.cnDrawRedToken);           
    }
private void StpRedBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawRedToken != null)
        {
            this.cnDrawRedToken.Cancel();
        }          
    }

    private void BlackBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawBlackToken.IsCancellationRequested)
        {
            this.cnDrawBlackToken = null;
            this.cnDrawBlackToken = new CancellationTokenSource();
        }             
        RunDrawingTask(SetSpecificPen(Color.Black), this.cnDrawBlackToken);
    }

    private void StpBlkBtn_Click(object sender, EventArgs e)
    {
        if (this.cnDrawBlackToken != null)
        {
            this.cnDrawBlackToken.Cancel();                
        }
    }

    private void RunDrawingTask(Pen specificPen, CancellationTokenSource cnTokenSource) 
    {
        int height = this.panel1.Height;
        int width = this.panel1.Width;
        Random random = new Random();
        Rectangle rectangle = InitRectangleWidthAndHeights();

        Task t = Task.Factory.StartNew(() => DrawRectangles(height, width, random, rectangle, specificPen, cnTokenSource.Token), cnTokenSource.Token);
    }

【问题讨论】:

    标签: c# winforms task-parallel-library


    【解决方案1】:

    您的逻辑不符合您的要求。解决方案之一: 您需要创建两个 CancelationTokenSource:redCancelationTokenSourceblackCancelationokenSource。并像这样重写RunDrawingTask 方法:

        private void RunDrawingTask(Pen pen, out CancellationTokenSource cnTokenSource)
        {
            int height = panel1.Height;
            int width = panel1.Width;
            Random random = new Random();
            Rectangle rectangle = InitRectangleWidthAndHeights();
    
            var cts = pen == blackPen ? blackTokenSource : redTokenSource;
            Task.Factory.StartNew(() => DrawRectangles(height, width, random, rectangle, pen, cts.Token), cts.Token);
        }
    
        CancellationTokenSource blackTokenSource = new CancellationTokenSource();
        CancellationTokenSource redTokenSource = new CancellationTokenSource();
    
        Pen blackPen = new Pen(Color.Black);
        Pen redPen = new Pen(Color.Red);
    

    当您想停止绘制动作时,您必须在 CancelationTokenSources 之一上调用“取消”方法:redCancelationSource 或 blackCancelationSource。

    【讨论】:

    • 也许我在你的例子中遗漏了一些东西,但是在这里取消指定的 cnTokenSource 不会对 blackTokenSource 和 redTokenSource 产生任何影响,对吧?
    • 不,你错了。查看所有应用程序,我们只有两个 cts。在这一行var cts = pen == blackPen ? blackTokenSource : redTokenSource 我们检测,我们现在必须使用哪些cts(取决于笔)。因此,如果您发送 redPen - 将使用redCancelationToken 执行新任务。因此,当您取消 RedCancelationTokenSource 时,所有具有此 cts 的任务都将中止。好吗??
    【解决方案2】:

    问题的根源在于,如果您单击运行任务的按钮 3 次,您将使用 3 个不同的取消令牌源运行 3 个任务。

    由于RunDrawingTask 中的这一行,您将覆盖刚刚传递给RunDrawingTask 的取消令牌

    cnTokenSource = new CancellationTokenSource();
    

    如果您删除该行,您将每次传递相同的令牌,并且您的所有任务都将能够对取消做出反应。

    【讨论】:

    • 感谢您的回复。如果我接受您的解决方案,它将导致其他问题。如果在 stop 单击后我想继续绘图并再次单击 Draw red 我必须为所有新的 Draw red 任务更新 CancellationTokenSource(已取消)。我会把这个代码cnTokenSource = new CancellationTokenSource();放在哪里?
    • RedBtn_ClickBlackBtn_Click方法中,可以观察到相应CTS的属性IsCancellationRequested(msdn.microsoft.com/en-us/library/…)。如果设置为 true,您可以创建 Cancellation Token Source 的新实例,设置适当的字段,并将其传递给 RunDrawingTask 方法。
    • 萨拉莱谢谢。现在,在重写 click 方法后效果很好!我会用重写的方法更新我的问题。
    猜你喜欢
    • 2011-09-10
    • 2016-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-01
    相关资源
    最近更新 更多