时候触发不当行为

本文关键字:不当行为 候触发 | 更新日期: 2023-09-27 18:16:22

当我使用CancellationTokenSource取消任务后等待任务时,我遇到了一个问题。cancel调用不会中断任务。当我等待对于任务,主线程阻塞,因为任务将永远不会中断。

下面是我的程序的简短描述:一个任务增加一个字符变量(从'A'到'Z'),并在GUI线程上显示它。为了做到这一点,任务在创建控件的线程上执行委托(this.invoke())。

只要我注释掉RefreshTextBox()-Function,取消调用就会起作用,任务将被中断。似乎this.invoke()命令可以防止任务中断。

在下面的代码中,我也用普通线程实现了相同的功能。然后就行了。任务实现和线程实现的区别在哪里?
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;
public partial class frm_Main : Form
{
    private delegate void dgt_StringHandler(string str_Value);
    CancellationTokenSource _obj_Cts = null;
    Thread _obj_Thread = null;
    Task _obj_Task = null;
    public frm_Main()
    {
        InitializeComponent();
    }
    private void CreateChar(ref char chr_Value)
    {
        int int_Value;
        int_Value = (int)chr_Value;
        int_Value++;
        if (int_Value > 90 || int_Value < 65)
            int_Value = 65;
        chr_Value = (char)int_Value;
    }
    private void TestThread()
    {
        char chr_Value = '@';
        bool bol_Stop = false;
        while (!bol_Stop)
        {
            try
            {
                Thread.Sleep(300);
                CreateChar(ref chr_Value);
                RefreshTextBox(chr_Value.ToString());
            }
            catch (ThreadInterruptedException)
            {
                bol_Stop = true;
            }
        }
    }
    private void TestTask(object obj_TokenTmp)
    {
        char chr_Value = '@';
        CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
        while (!obj_Token.IsCancellationRequested)
        {
            Thread.Sleep(300);
            CreateChar(ref chr_Value);
            RefreshTextBox(chr_Value.ToString());
        }
    }
    private void RefreshTextBox(string str_Value)
    {
        if (txt_Value.InvokeRequired)
        {
            dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
            this.Invoke(obj_StringHandler, new object[] { str_Value });
        }
        else
        {
            txt_Value.Text = str_Value;
        }
    }
    private void btn_StartStop_Click(object sender, EventArgs e)
    {
        if (_obj_Task == null && _obj_Thread == null)
        {
            if (opt_Task.Checked)
            {
                _obj_Cts = new CancellationTokenSource();
                _obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
                _obj_Task.Start();
            }
            else
            {
                _obj_Thread = new Thread(new ThreadStart(TestThread));
                _obj_Thread.Start();
            }
            btn_StartStop.Text = "Stop";
        }
        else
        {
            if (_obj_Thread != null)
            {
                _obj_Thread.Interrupt();
                _obj_Thread.Join();
                _obj_Thread = null;
            }
            if (_obj_Task != null)
            {
                _obj_Cts.Cancel();
                _obj_Task.Wait();
                _obj_Task = null;
                _obj_Cts = null;
            }
            btn_StartStop.Text = "Start";
        }
    }
}

时候触发不当行为

这两段代码一起形成了一个死锁:

_obj_Cts.Cancel();
_obj_Task.Wait();

this.Invoke(obj_StringHandler, new object[] { str_Value });

您在主线程上调用Wait(),并且Invoke()需要由主线程处理。

可以使用this.BeginInvoke(...)来打破死锁。

Thread版本使用了Interrupt,一个大锤。所以线程不会尝试在停止信号后调用RefreshTextBox()

以下是改编后的代码。我现在调用BeginInvoke()而不是像Henk Holterman建议的那样调用Invoke()。这非常有效,也是防止死锁的唯一正确方法。还有另一种情况必须考虑。我也有一个IAsyncResult对象是通过BeginInvoke()调用给出的。我用这个对象来检查异步调用是否已经完成。如果我不检查它,可能是GUI线程不够快(例如GUI线程中的某个睡眠语句)来执行我的委托,并且导致我的TestTask()方法总是调用BeginInvoke(),尽管GUI线程还没有完成最后一个委托。结果是我的GUI线程会阻塞应用程序。

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading;
    using System.Threading.Tasks;
    namespace InvokeTest
    {
    public partial class frm_Main : Form
    {
    private delegate void dgt_StringHandler(string str_Value);
    CancellationTokenSource _obj_Cts = null;
    Thread _obj_Thread = null;
    Task _obj_Task = null;
    IAsyncResult _obj_Ar = null;
    public frm_Main()
    {
        InitializeComponent();
    }
    private void CreateChar(ref char chr_Value)
    {
        int int_Value;
        int_Value = (int)chr_Value;
        int_Value++;
        if (int_Value > 90 || int_Value < 65)
            int_Value = 65;
        chr_Value = (char)int_Value;
    }

    private void TestThread()
    {
        char chr_Value = '@';
        bool bol_Stop = false;
        while (!bol_Stop)
        {
            try
            {
                Thread.Sleep(1); // is needed for interrupting the thread
                CreateChar(ref chr_Value);
                RefreshTextBox(chr_Value.ToString());
            }
            catch (ThreadInterruptedException)
            {
                bol_Stop = true;
            }
        }
    }
    private void TestTask(object obj_TokenTmp)
    {
        char chr_Value = '@';
        CancellationToken obj_Token = (CancellationToken)obj_TokenTmp;
        while (!obj_Token.IsCancellationRequested)
        {
            CreateChar(ref chr_Value);
            RefreshTextBox(chr_Value.ToString());
        }
    }

    private void RefreshTextBox(string str_Value)
    {            
        if (txt_Value.InvokeRequired)
        {
            if (_obj_Ar == null ||
                _obj_Ar.IsCompleted)
            {
                dgt_StringHandler obj_StringHandler = new dgt_StringHandler(RefreshTextBox);
                _obj_Ar = this.BeginInvoke(obj_StringHandler, new object[] { str_Value });
            }
        }
        else
        {
            Thread.Sleep(200);
            txt_Value.Text = str_Value;
        }
    }

    private void btn_StartStop_Click(object sender, EventArgs e)
    {
        if (_obj_Task == null && _obj_Thread == null)
        {
            if (opt_Task.Checked)
            {
                _obj_Cts = new CancellationTokenSource();
                _obj_Task = new Task(new Action<object>(TestTask), _obj_Cts.Token, _obj_Cts.Token);
                _obj_Task.Start();
            }
            else
            {
                _obj_Thread = new Thread(new ThreadStart(TestThread));
                _obj_Thread.Start();
            }
            btn_StartStop.Text = "Stop";
        }
        else
        {
            if (_obj_Thread != null)
            {
                _obj_Thread.Interrupt();
                _obj_Thread.Join();
                _obj_Thread = null;
            }
            if (_obj_Task != null)
            {
                _obj_Cts.Cancel();
                _obj_Task.Wait();
                _obj_Task = null;
                _obj_Cts = null;
            }
            btn_StartStop.Text = "Start";
        }
    }
    private void frm_Main_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (_obj_Thread != null)
        {
            _obj_Thread.Interrupt();
            _obj_Thread.Join();
        }
        if (_obj_Task != null)
        {
            _obj_Cts.Cancel();
            _obj_Task.Wait();
        }
    }
    }
    }