Nice programing

스레드 중단 (Thread.Abort 메서드)과 같은 작업을 중단 할 수 있습니까?

nicepro 2020. 12. 29. 08:26
반응형

스레드 중단 (Thread.Abort 메서드)과 같은 작업을 중단 할 수 있습니까?


다음과 같은 스레드를 중단 할 수 있습니다.

Thread thread = new Thread(SomeMethod);
.
.
.
thread.Abort();

그러나 취소 메커니즘이 아닌 동일한 방식으로 작업 (.Net 4.0)을 중단 할 수 있습니다. 태스크를 즉시 죽이고 싶습니다.


  1. Thread.Abort ()를 사용하면 안됩니다.
  2. 작업은 취소 할 수 있지만 중단 할 수는 없습니다.

Thread.Abort () 메소드가 (심각)는 지원되지 않습니다.

스레드와 태스크는 모두 중지 될 때 협력해야합니다. 그렇지 않으면 시스템이 불안정하거나 정의되지 않은 상태로 남겨질 위험이 있습니다.

Process를 실행하고 외부에서 종료해야하는 경우 유일한 안전한 옵션은 별도의 AppDomain에서 실행하는 것입니다.


스레드 중단을 사용하지 않는 것에 대한 지침은 논란의 여지가 있습니다. 아직 장소는 있지만 예외적 인 상황이라고 생각합니다. 그러나 항상 주변을 디자인하고 최후의 수단으로 간주해야합니다.

예;

차단 동기 웹 서비스에 연결하는 간단한 Windows 양식 응용 프로그램이 있습니다. 병렬 루프 내에서 웹 서비스의 함수를 실행합니다.

CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{

    Thread.Sleep(120000); // pretend web service call

});

이 예에서 차단 호출을 완료하는 데 2 ​​분이 걸립니다. 이제 MaxDegreeOfParallelism을 ProcessorCount라고 설정했습니다. iListOfItems에는 처리 할 1000 개의 항목이 있습니다.

사용자가 프로세스 버튼을 클릭하고 루프가 시작되면 iListOfItems 컬렉션의 1000 개 항목에 대해 '최대'20 개의 스레드가 실행됩니다. 각 반복은 자체 스레드에서 실행됩니다. 각 스레드는 Parallel.ForEach에 의해 생성 될 때 전경 스레드를 활용합니다. 즉, 기본 애플리케이션 종료에 관계없이 모든 스레드가 완료 될 때까지 앱 도메인이 유지됩니다.

그러나 사용자는 어떤 이유로 응용 프로그램을 닫아야합니다. 예를 들어 양식을 닫습니다. 이 20 개의 스레드는 1000 개의 항목이 모두 처리 될 때까지 계속 실행됩니다. 이 시나리오에서는 응용 프로그램이 사용자가 예상 한대로 종료되지 않고 작업 관리자를 살펴보면 알 수 있듯이 백그라운드에서 계속 실행되므로 이상적이지 않습니다.

사용자가 앱을 다시 빌드하려고하면 (VS 2010), exe가 잠겨 있다고보고 한 다음 작업 관리자로 이동하여 앱을 종료하거나 1000 개 항목이 모두 처리 될 때까지 기다려야합니다.

나는 당신을 비난하지 않을 것이지만 물론입니다! CancellationTokenSource 개체를 사용하여 이러한 스레드를 취소 하고 Cancel ...을 호출해야하지만 .net 4.0부터이 문제가 몇 가지 있습니다. 첫째, 이것은 여전히 ​​중단 예외를 제공하고 스레드 종료를 제공하는 스레드 중단을 초래하지 않으므로 앱 도메인은 대신 스레드가 정상적으로 완료 될 때까지 기다려야하며 이는 마지막 차단 호출을 기다리는 것을 의미합니다. 궁극적으로을 호출하는 마지막 실행 반복 (스레드)입니다 po.CancellationToken.ThrowIfCancellationRequested. 이 예에서 이는 양식이 닫히고 취소가 호출 되었더라도 앱 도메인이 최대 2 분 동안 활성 상태를 유지할 수 있음을 의미합니다.

CancellationTokenSource에서 Cancel을 호출하면 처리 스레드에서 예외가 발생하지 않으므로 실제로 스레드 중단과 유사한 차단 호출을 중단하고 실행을 중지하는 역할을합니다. 다른 모든 스레드 (동시 반복)가 결국 완료되고 반환 될 때 예외가 캐시됩니다. 예외는 시작 스레드 (루프가 선언 된 위치)에서 throw됩니다.

CancellationTokenSource 개체에 취소 옵션 을 사용 하지 않기로 선택했습니다 . 이것은 낭비이며 예외로 코드 흐름을 제어하는 ​​잘 알려진 안티 패튼을 위반하는 것입니다.

대신 간단한 스레드 안전 속성 즉, Bool stopExecuting을 구현하는 것이 '더 낫습니다'. 그런 다음 루프 내에서 stopExecuting 값을 확인하고 값이 외부 영향에 의해 true로 설정되면 대체 경로를 사용하여 정상적으로 종료 할 수 있습니다. cancel을 호출해서는 안되므로 다른 옵션이 될 수있는 CancellationTokenSource.IsCancellationRequested확인할 수 없습니다.

루프 내에서 조건이 적절할 경우 다음과 같은 것;

if (loopState.ShouldExitCurrentIteration || loopState.IsExceptional || stopExecuting) {loopState.Stop (); 반환;}

반복은 이제 '제어 된'방식으로 종료되고 추가 반복을 종료하지만 제가 말했듯이 이것은 각 반복 내에서 수행되는 장기 실행 및 차단 호출을 기다려야하는 문제에 거의 영향을주지 않습니다 ( 병렬 루프 스레드), 각 스레드가 중지해야하는지 확인하는 옵션에 도달하기 전에 완료해야하기 때문입니다.

요약하면 사용자가 양식을 닫을 때 20 개의 스레드는 stopExecuting을 통해 중지하라는 신호를 받게되지만 장기 실행 함수 호출 실행이 완료되면 중지됩니다.

애플리케이션 도메인이 항상 살아 있고 모든 포 그라운드 스레드가 완료되었을 때만 해제된다는 사실에 대해 우리는 아무것도 할 수 없습니다. 이것은 루프 내에서 이루어진 모든 차단 호출이 완료되기를 기다리는 것과 관련된 지연이 있음을 의미합니다.

진정한 스레드 중단 만 차단 호출을 중단 할 수 있으며, 중단 된 스레드의 예외 처리기에서 할 수있는 최선의 방법으로 시스템이 불안정한 / 정의되지 않은 상태로 남겨지는 것을 완화해야합니다. 이것이 적절한 지 여부는 프로그래머가 유지하기 위해 선택한 리소스 핸들과 스레드의 finally 블록에서 닫는 것이 얼마나 쉬운 지에 따라 결정할 문제입니다. 세미 해결 방법으로 취소시 종료 할 토큰으로 등록 할 수 있습니다.

CancellationTokenSource cts = new CancellationTokenSource();
ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;

Parallel.ForEach(iListOfItems, po, (item, loopState) =>
{

    using (cts.Token.Register(Thread.CurrentThread.Abort))
    {
        Try
        {
           Thread.Sleep(120000); // pretend web service call          
        }
        Catch(ThreadAbortException ex)
        {
           // log etc.
        }
        Finally
        {
          // clean up here
        }
    }

});

그러나 선언 스레드에서 여전히 예외가 발생합니다.

모든 것을 고려하면 parallel.loop 구문을 사용하는 인터럽트 차단 호출은 라이브러리의 더 모호한 부분을 사용하지 않도록 옵션에 대한 방법이 될 수 있습니다. 그러나 선언 방법에서 예외를 던지거나 취소 할 수있는 옵션이없는 이유는 저를 감독 할 수있는 가능성이 있습니다.


그러나 취소 메커니즘이 아닌 동일한 방식으로 작업 (.Net 4.0)을 중단 할 수 있습니다. 태스크를 즉시 종료하고 싶습니다 .

다른 응답자들은 그렇게하지 말라고했습니다. 하지만 네, 할 수 있습니다 . Thread.Abort()Task의 취소 메커니즘에 의해 호출 될 델리게이트로 제공 할 수 있습니다 . 이를 구성하는 방법은 다음과 같습니다.

class HardAborter
{
  public bool WasAborted { get; private set; }
  private CancellationTokenSource Canceller { get; set; }
  private Task<object> Worker { get; set; }

  public void Start(Func<object> DoFunc)
  {
    WasAborted = false;

    // start a task with a means to do a hard abort (unsafe!)
    Canceller = new CancellationTokenSource();

    Worker = Task.Factory.StartNew(() => 
      {
        try
        {
          // specify this thread's Abort() as the cancel delegate
          using (Canceller.Token.Register(Thread.CurrentThread.Abort))
          {
            return DoFunc();
          }
        }
        catch (ThreadAbortException)
        {
          WasAborted = true;
          return false;
        }
      }, Canceller.Token);
  }

  public void Abort()
  {
    Canceller.Cancel();
  }

}

면책 조항 : 이러지 마십시오.

다음은하지 말아야 할 일의 예입니다.

 var doNotDoThis = new HardAborter();

 // start a thread writing to the console
 doNotDoThis.Start(() =>
    {
       while (true)
       {
          Thread.Sleep(100);
          Console.Write(".");
       }
       return null;
    });


 // wait a second to see some output and show the WasAborted value as false
 Thread.Sleep(1000);
 Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted);

 // wait another second, abort, and print the time
 Thread.Sleep(1000);
 doNotDoThis.Abort();
 Console.WriteLine("Abort triggered at " + DateTime.Now);

 // wait until the abort finishes and print the time
 while (!doNotDoThis.WasAborted) { Thread.CurrentThread.Join(0); }
 Console.WriteLine("WasAborted: " + doNotDoThis.WasAborted + " at " + DateTime.Now);

 Console.ReadKey();

샘플 코드의 출력


누구나 스레드를 종료하는 것이 나쁘다는 것을 알고 있습니다. 문제는 호출하는 코드를 소유하지 않은 경우입니다. 이 코드가 일부 do / while 무한 루프에서 실행되고 자체적으로 일부 기본 함수를 호출하는 경우 기본적으로 멈춰 있습니다. 자신의 코드 종료, 중지 또는 Dispose 호출에서 이런 일이 발생하면 나쁜 놈을 쏘는 것이 좋습니다 (그러므로 스스로 나쁜 놈이되지 않도록).

그래서 그만한 가치가 있기 때문에 풀의 스레드 나 CLR에 의해 생성 된 일부 스레드가 아닌 자체 네이티브 스레드를 사용하는 두 개의 차단 함수를 작성했습니다. 시간 초과가 발생하면 스레드를 중지합니다.

// returns true if the call went to completion successfully, false otherwise
public static bool RunWithAbort(this Action action, int milliseconds) => RunWithAbort(action, new TimeSpan(0, 0, 0, 0, milliseconds));
public static bool RunWithAbort(this Action action, TimeSpan delay)
{
    if (action == null)
        throw new ArgumentNullException(nameof(action));

    var source = new CancellationTokenSource(delay);
    var success = false;
    var handle = IntPtr.Zero;
    var fn = new Action(() =>
    {
        using (source.Token.Register(() => TerminateThread(handle, 0)))
        {
            action();
            success = true;
        }
    });

    handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
    WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
    CloseHandle(handle);
    return success;
}

// returns what's the function should return if the call went to completion successfully, default(T) otherwise
public static T RunWithAbort<T>(this Func<T> func, int milliseconds) => RunWithAbort(func, new TimeSpan(0, 0, 0, 0, milliseconds));
public static T RunWithAbort<T>(this Func<T> func, TimeSpan delay)
{
    if (func == null)
        throw new ArgumentNullException(nameof(func));

    var source = new CancellationTokenSource(delay);
    var item = default(T);
    var handle = IntPtr.Zero;
    var fn = new Action(() =>
    {
        using (source.Token.Register(() => TerminateThread(handle, 0)))
        {
            item = func();
        }
    });

    handle = CreateThread(IntPtr.Zero, IntPtr.Zero, fn, IntPtr.Zero, 0, out var id);
    WaitForSingleObject(handle, 100 + (int)delay.TotalMilliseconds);
    CloseHandle(handle);
    return item;
}

[DllImport("kernel32")]
private static extern bool TerminateThread(IntPtr hThread, int dwExitCode);

[DllImport("kernel32")]
private static extern IntPtr CreateThread(IntPtr lpThreadAttributes, IntPtr dwStackSize, Delegate lpStartAddress, IntPtr lpParameter, int dwCreationFlags, out int lpThreadId);

[DllImport("kernel32")]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32")]
private static extern int WaitForSingleObject(IntPtr hHandle, int dwMilliseconds);

While it's possible to abort a thread, in practice it's almost always a very bad idea to do so. Aborthing a thread means the thread is not given a chance to clean up after itself, leaving resources undeleted, and things in unknown states.

In practice, if you abort a thread, you should only do so in conjunction with killing the process. Sadly, all too many people think ThreadAbort is a viable way of stopping something and continuing on, it's not.

Since Tasks run as threads, you can call ThreadAbort on them, but as with generic threads you almost never want to do this, except as a last resort.


using System;
using System.Threading;
using System.Threading.Tasks;

...

var cts = new CancellationTokenSource();
var task = Task.Run(() => { while (true) { } });
Parallel.Invoke(() =>
{
    task.Wait(cts.Token);
}, () =>
{
    Thread.Sleep(1000);
    cts.Cancel();
});

This is a simple snippet to abort a never-ending task with CancellationTokenSource.


If you have Task constructor, then we may extract Thread from the Task, and invoke thread.abort.

Thread th = null;

Task.Factory.StartNew(() =>
{
    th = Thread.CurrentThread;

    while (true)
    {
        Console.WriteLine(DateTime.UtcNow);
    }
});

Thread.Sleep(2000);
th.Abort();
Console.ReadKey();

참조 URL : https://stackoverflow.com/questions/4359910/is-it-possible-to-abort-a-task-like-aborting-a-thread-thread-abort-method

반응형