2011-12-02 3 views
1

CancellationTokenSource로 취소 한 후 작업을 기다릴 때 문제가 있습니다. 취소 호출은 작업을 방해하지 않습니다. 작업이 중단되지 않으므로 주 스레드가 차단하는 작업에 대해 을 대기합니다.CancellationTokenSource misbehavior

내 프로그램에 대한 간단한 설명은 다음과 같습니다. 작업은 char 변수 ('A'에서 'Z'까지)를 증가시키고 GUI 스레드에 표시합니다. 이 작업을 수행하기 위해 컨트롤이 작성된 스레드에서 대리자 (this.invoke())를 실행합니다.

RefreshTextBox() 함수를 주석 처리하는 즉시 취소 호출이 작동하고 작업이 중단됩니다. 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"; 
     } 
    } 
} 

답변

1

코드의이 2 개는 함께 교착 형성 : 당신은 주 스레드에서 Wait()를 호출

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

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

을하고, 호출은()에 필요 주 스레드가 처리해야합니다.

대신 this.BeginInvoke(...)을 사용하여 교착 상태를 해제 할 수 있습니다.

스레드 버전은 쓰레드 해머 인 인터럽트를 사용합니다. 따라서 스레드는 중지 신호 후 RefreshTextBox()에 전화를 시도하지 않습니다.

+0

답장을 보내 주셔서 감사합니다. 그러나 이것은 문제의 해결책이 아닙니다. CancellationTokenSource로 취소 할 수없는 이유를 알고 싶습니다. Wait 문을 주석 처리 할 때 작업이 백그라운드에서 계속 실행되고있는 경우 (또는 내 경우에는 블로킹) 백그라운드에서 실행되지 않고 끝나지 않습니다. –

+0

Interrupt() 및 Join() 호출시 같은 루틴이 일반 스레드에서 작동하는 이유는 분명하지 않습니다. 그리고 그것을 작업으로 구현하고 Cancel()을 호출 한 다음 Wait()을 호출하면 작동하지 않습니다. 차이점은 어디입니까? refreshbox() 메서드를 주석 처리 할 때 Cancel() 및 Wait()도 작업 구현과 함께 작동합니다! –

+0

"작업이 아직 실행 중입니다."- 그러면 문제가 있습니다. 취소는 협동 작업입니다. 당신의 과제는 끝나야합니다. Thread.Interrupt (또는 .Abort)는 강력하지만 안전하지 않습니다. –

0

다음은 적응 코드입니다. 이제 Henk Holterman이 제안한대로 Invoke() 대신 BeginInvoke()를 호출합니다. 이것은 매우 잘 작동하며 교착 상태를 방지하는 유일한 방법입니다. 또한 고려해야 할 또 다른 경우가 있습니다. 또한 BeginInvoke() 호출을 통해 제공되는 IAsyncResult 개체가 있습니다. 비동기 호출이 이미 완료되었는지 여부를 확인하는 데 사용하는이 객체입니다. 그것을 확인하지 않을 경우 GUI 스레드가 충분히 빨리 (예를 들어, GUI 스레드의 어딘가에 sleep 문을) 내 대리자를 실행하고 내 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(); 
     } 
    } 

    } 
    }