문제

I have a problem when i wait for a task after i have canceled it with the CancellationTokenSource. The cancel call does not interrupt the task. When i wait for the task the main thread blocks because the task will be never interrupted.

Here is a short description my program: A task increments a char variable (from 'A' to 'Z') and shows it on the GUI thread. In order to do this the task executes a delegate (this.invoke()) on the thread the control was created on.

As soon as i comment out the RefreshTextBox()-Function the cancel call works and the task will be interrupted. It seems as if the this.invoke() command prevents the task from interrupting.

I the code below i have also implemented the same functionality with normal threads. And then i works. Where is the difference between task implementation and thread implementation?

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";
        }
    }
}
도움이 되었습니까?

해결책

These 2 pieces of the code together form a deadlock:

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

and

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

You are calling Wait() on the main thread, and Invoke() needs to be handled by the main thread.

You can break the deadlock by using this.BeginInvoke(...) instead.

The Thread version uses Interrupt, a sledgehammer. So the thread won't try to call RefreshTextBox() after the stop signal.

다른 팁

Here is the adapted code. I now call BeginInvoke() instead of Invoke() as Henk Holterman has proposed. This works very fine and is the only right way to prevent a deadlock. There is also another case that must be considered. I have also a IAsyncResult object that is given through the BeginInvoke() call. This object I use to check whether the async call has already completed or not. If I wouldn't check it it could be that the GUI thread is not fast enough (for example a sleep statement somewhere in the GUI thread) to execute my delegate and cause of this my TestTask() method would always call BeginInvoke() although the GUI thread has not already completed the last delegate. The result would be that my GUI thread would block the application.

    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();
        }
    }

    }
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top