TPL 작업을 중단 / 취소하려면 어떻게합니까?


156

스레드에서 일부 System.Threading.Task작업을 만들고 각 작업을 시작합니다.

.Abort()스레드를 죽이려고 할 때 작업이 중단되지 않습니다.

.Abort()내 작업에 어떻게 전송 합니까?


답변:


228

당신은 할 수 없습니다. 작업은 스레드 풀의 백그라운드 스레드를 사용합니다. 또한 Abort 메서드를 사용하여 스레드를 취소하지 않는 것이 좋습니다. 취소 토큰을 사용하여 작업을 취소하는 적절한 방법을 설명하는 다음 블로그 게시물 을 살펴볼 수 있습니다 . 예를 들면 다음과 같습니다.

class Program
{
    static void Main()
    {
        var ts = new CancellationTokenSource();
        CancellationToken ct = ts.Token;
        Task.Factory.StartNew(() =>
        {
            while (true)
            {
                // do some heavy work here
                Thread.Sleep(100);
                if (ct.IsCancellationRequested)
                {
                    // another thread decided to cancel
                    Console.WriteLine("task canceled");
                    break;
                }
            }
        }, ct);

        // Simulate waiting 3s for the task to complete
        Thread.Sleep(3000);

        // Can't wait anymore => cancel this task 
        ts.Cancel();
        Console.ReadLine();
    }
}

5
좋은 설명입니다. 질문이 있습니다 .Task.Factory.StartNew에 익명 메소드가 없으면 어떻게 작동합니까? 와 같은 Task.Factory.StartNew (() => ProcessMyMethod (), cancellationToken)
Prerak K

61
실행중인 작업 내에서 반환되지 않는 차단 호출이있는 경우 어떻게합니까?
mehmet6parmak

3
@ mehmet6parmak 당신이 할 수있는 유일한 일은 Task.Wait(TimeSpan / int)외부에서 (시간 기반) 마감일을주는 것입니다.
Mark

2
새로운 내부에서 메소드 실행을 관리하기 위해 사용자 정의 클래스가 있으면 어떻게 Task됩니까? 같은 것 : public int StartNewTask(Action method). 내부 StartNewTask방법을 나는 새를 생성 Task하여 : Task task = new Task(() => method()); task.Start();. 그래서 어떻게 관리 할 수 CancellationToken있습니까? 또한 Thread여전히 걸려있는 일부 작업이 있는지 확인하기 위해 논리를 구현 해야하는지 알고 싶습니다 Form.Closing. 와 함께 Threads사용 Thread.Abort()합니다.
Cheshire Cat

세상에, 정말 나쁜 예입니다! 그것은 단순한 부울 조건입니다. 물론 그것은 시도 할 첫 번째 것입니다! 그러나 예를 들어 별도의 작업에 기능이 있습니다. 종료하는 데 많은 시간이 걸릴 수 있으며 이상적으로 스레딩 또는 기타 사항에 대해 알지 않아야합니다. 조언으로 기능을 어떻게 취소합니까?
Hi-Angel

32

작업이 실행중인 스레드를 캡처하면 작업을 중단 할 수 있습니다. 다음은이를 보여주는 예제 코드입니다.

void Main()
{
    Thread thread = null;

    Task t = Task.Run(() => 
    {
        //Capture the thread
        thread = Thread.CurrentThread;

        //Simulate work (usually from 3rd party code)
        Thread.Sleep(1000);

        //If you comment out thread.Abort(), then this will be displayed
        Console.WriteLine("Task finished!");
    });

    //This is needed in the example to avoid thread being still NULL
    Thread.Sleep(10);

    //Cancel the task by aborting the thread
    thread.Abort();
}

필자는 Task.Run ()을 사용하여 가장 일반적인 유스 케이스를 보여주었습니다. 취소 여부를 결정하기 위해 CancellationTokenSource 클래스를 사용하지 않는 오래된 단일 스레드 코드가있는 작업의 편의를 사용했습니다.


2
이 아이디어에 감사드립니다. 이 접근 방식을 사용하여 일부 외부 코드에 대한 시간 초과를 구현 CancellationToken
Christoph Fink

7
AFAIK thread.abort는 스택에 대해 알 수없는 상태로 남겨 둘 수 있습니다. 나는 그것을 시도한 적이 없지만 thread.abort가 저장 될 별도의 응용 프로그램 도메인에서 스레드를 시작하는 것 같습니다! 또한 하나의 작업을 중단하기 위해 솔루션에 전체 스레드가 낭비됩니다. 처음에는 작업이 아니라 스레드를 사용해야합니다. (downvote)
Martin Meeser

1
내가 쓴 것처럼-이 솔루션은 특정 상황에서 고려 될 수있는 최후의 수단입니다. 물론 CancellationToken경쟁 조건이없는 간단한 솔루션도 고려해야합니다. 위의 코드는 사용 영역이 아니라 방법 만 설명합니다.
Florian Rappl

8
이 접근법은 알 수없는 결과가있을 수 있다고 생각하며 프로덕션 코드에서는 권장하지 않습니다. 작업이 항상 다른 스레드에서 실행되도록 보장되는 것은 아닙니다. 즉, 스케줄러가 결정한 경우 작업을 생성 한 스레드와 동일한 스레드에서 실행될 수 있습니다 (주 스레드가 thread로컬 변수 로 전달됨을 의미 함 ). 코드에서 메인 스레드를 중단시킬 수 있습니다. 실제로 원하는 것은 아닙니다. 중단을 주장하는 경우 중단이 발생하기 전에 스레드가 동일한 지 확인하는 것이 좋습니다.
Ivaylo Slavov

10
@Martin-SO에 대한이 질문을 조사한 결과 Thread.Abort를 사용하여 작업을 종료하는 몇 가지 답변을 다운 투표했지만 다른 해결책은 제공하지 않았습니다. 취소를 지원하지 않고 작업에서 실행되는 타사 코드를 어떻게 죽일 수 있습니까?
Gordon Bean

32

마찬가지로 이 게시물 제안, 이것은 다음과 같은 방법으로 수행 할 수 있습니다 :

int Foo(CancellationToken token)
{
    Thread t = Thread.CurrentThread;
    using (token.Register(t.Abort))
    {
        // compute-bound work here
    }
}

작동하지만 그러한 방법을 사용하지 않는 것이 좋습니다. 작업에서 실행되는 코드를 제어 할 수 있으면 적절한 취소 처리를 수행하는 것이 좋습니다.


6
대체 방법을 제시하면서 다른 접근 방식을 제공 한 +1 나는 이것이 가능하다는 것을 몰랐다 :)
Joel

1
솔루션 주셔서 감사합니다! 메소드에서 스레드 인스턴스를 가져 와서이 인스턴스를 직접 중단하는 대신 토큰을 메소드에 전달하고 토큰 소스를 취소 할 수 있습니다.
Sam

중단 된 작업 스레드는 스레드 풀 스레드이거나 작업을 호출하는 호출자의 스레드 일 수 있습니다. 이를 피하기 위해 TaskScheduler를 사용하여 작업 전용 스레드를 지정할있습니다 .
Edward Brey

19

이런 종류의 일은 왜 Abort더 이상 사용되지 않는 물류상의 이유 중 하나입니다 . 우선, 가능하면 스레드를 취소하거나 중지하는 데 사용하지 마십시오 Thread.Abort(). Abort()적시에 중지하라는보다 평화로운 요청에 응답하지 않는 스레드를 강제로 종료하는 데만 사용해야합니다.

즉, 한 스레드가 설정하고 대기하는 동안 다른 스레드가 주기적으로 확인하고 정상적으로 종료되는 공유 취소 표시기를 제공해야합니다. .NET 4에는이 목적을 위해 특별히 설계된 구조가 포함되어 CancellationToken있습니다.


8

직접 시도하지 마십시오. CancellationToken 과 함께 작동하도록 작업을 설계하십시오. 하고이 방법으로 취소하십시오.

또한 CancellationToken을 통해 기본 스레드를 작동하도록 변경하는 것이 좋습니다. 전화 Thread.Abort()하는 것은 나쁜 생각입니다. 진단하기가 매우 어려운 다양한 문제가 발생할 수 있습니다. 대신, 해당 스레드는 작업에서 사용하는 것과 동일한 취소 를 사용할 CancellationTokenSource수 있으며 모든 작업 및 기본 스레드 의 취소를 트리거하는 데 사용될 수 있습니다 .

이것은 훨씬 더 단순하고 안전한 디자인으로 이어질 것입니다.


8

Task.Factory.StartNew ()에서 익명 메서드를 사용하지 않을 때 CancellationTokens를 사용하는 방법에 대한 Prerak K의 질문에 대답하기 위해 MSDN 예제에 표시된 것처럼 CancellationToken을 StartNew ()로 시작하는 메서드에 매개 변수로 전달합니다. 여기 .

예 :

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

Task.Factory.StartNew( () => DoSomeWork(1, token), token);

static void DoSomeWork(int taskNum, CancellationToken ct)
{
    // Do work here, checking and acting on ct.IsCancellationRequested where applicable, 

}

7

작업을 취소하기 위해 혼합 접근법을 사용합니다.

  • 첫째, 나는 사용하여 정중를 취소하기 위해 노력하고있어 예약 취소 .
  • 여전히 실행 중이면 (예 : 개발자의 실수로 인해) 구식 Abort 메소드를 사용하여 오작동하고 종료 합니다.

아래 예를 확인하십시오.

private CancellationTokenSource taskToken;
private AutoResetEvent awaitReplyOnRequestEvent = new AutoResetEvent(false);

void Main()
{
    // Start a task which is doing nothing but sleeps 1s
    LaunchTaskAsync();
    Thread.Sleep(100);
    // Stop the task
    StopTask();
}

/// <summary>
///     Launch task in a new thread
/// </summary>
void LaunchTaskAsync()
{
    taskToken = new CancellationTokenSource();
    Task.Factory.StartNew(() =>
        {
            try
            {   //Capture the thread
                runningTaskThread = Thread.CurrentThread;
                // Run the task
                if (taskToken.IsCancellationRequested || !awaitReplyOnRequestEvent.WaitOne(10000))
                    return;
                Console.WriteLine("Task finished!");
            }
            catch (Exception exc)
            {
                // Handle exception
            }
        }, taskToken.Token);
}

/// <summary>
///     Stop running task
/// </summary>
void StopTask()
{
    // Attempt to cancel the task politely
    if (taskToken != null)
    {
        if (taskToken.IsCancellationRequested)
            return;
        else
            taskToken.Cancel();
    }

    // Notify a waiting thread that an event has occurred
    if (awaitReplyOnRequestEvent != null)
        awaitReplyOnRequestEvent.Set();

    // If 1 sec later the task is still running, kill it cruelly
    if (runningTaskThread != null)
    {
        try
        {
            runningTaskThread.Join(TimeSpan.FromSeconds(1));
        }
        catch (Exception ex)
        {
            runningTaskThread.Abort();
        }
    }
}


4

a CancellationToken를 사용 하여 작업 취소 여부를 제어 할 수 있습니다 . 시작하기 전에 중단하거나 (이미이 작업을 수행 했음) 실제로 중단하는 것에 대해 이야기하고 있습니까? 전자의 경우 CancellationToken도움 이 될 수 있습니다. 후자의 경우 아마도 "베일 아웃"메커니즘을 구현하고 작업 실행의 적절한 지점에서 빠르게 실패해야하는지 여부를 확인해야합니다 (여전히 도움을주기 위해 CancellationToken을 사용할 수는 있지만 조금 더 수동적 임).

MSDN에는 작업 취소에 대한 기사가 있습니다. http://msdn.microsoft.com/en-us/library/dd997396.aspx


3

작업이 ThreadPool에서 실행되고있어 (적어도 기본 팩토리를 사용하는 경우) 스레드 중단은 작업에 영향을 미치지 않습니다. 작업을 중단하려면 msdn에서 작업 취소 를 참조하십시오 .


1

나는 시도 CancellationTokenSource했지만 이것을 할 수 없다. 그리고 나는 내 자신의 방식으로 이것을했습니다. 그리고 작동합니다.

namespace Blokick.Provider
{
    public class SignalRConnectProvider
    {
        public SignalRConnectProvider()
        {
        }

        public bool IsStopRequested { get; set; } = false; //1-)This is important and default `false`.

        public async Task<string> ConnectTab()
        {
            string messageText = "";
            for (int count = 1; count < 20; count++)
            {
                if (count == 1)
                {
                //Do stuff.
                }

                try
                {
                //Do stuff.
                }
                catch (Exception ex)
                {
                //Do stuff.
                }
                if (IsStopRequested) //3-)This is important. The control of the task stopping request. Must be true and in inside.
                {
                    return messageText = "Task stopped."; //4-) And so return and exit the code and task.
                }
                if (Connected)
                {
                //Do stuff.
                }
                if (count == 19)
                {
                //Do stuff.
                }
            }
            return messageText;
        }
    }
}

그리고 메소드 호출의 또 다른 클래스 :

namespace Blokick.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MessagePerson : ContentPage
    {
        SignalRConnectProvider signalR = new SignalRConnectProvider();

        public MessagePerson()
        {
            InitializeComponent();

            signalR.IsStopRequested = true; // 2-) And this. Make true if running the task and go inside if statement of the IsStopRequested property.

            if (signalR.ChatHubProxy != null)
            {
                 signalR.Disconnect();
            }

            LoadSignalRMessage();
        }
    }
}

0

당신이 작업이 자신의 스레드에서 생성의 원인이 될 수 있고, 호출 할 경우 스레드와 같은 작업을 중단 할 수 있습니다 Abort에의Thread 개체를 . 기본적으로 작업은 스레드 풀 스레드 또는 호출 스레드에서 실행되며 일반적으로 중단하지 않습니다.

작업이 자체 스레드를 갖도록하려면에서 파생 된 사용자 지정 스케줄러를 만듭니다 TaskScheduler. 의 구현 QueueTask에서 새 스레드를 작성하고이를 사용하여 태스크를 실행하십시오. 나중에 스레드를 중단하면 작업이 오류 상태에서 완료됩니다 ThreadAbortException.

이 작업 스케줄러를 사용하십시오.

class SingleThreadTaskScheduler : TaskScheduler
{
    public Thread TaskThread { get; private set; }

    protected override void QueueTask(Task task)
    {
        TaskThread = new Thread(() => TryExecuteTask(task));
        TaskThread.Start();
    }

    protected override IEnumerable<Task> GetScheduledTasks() => throw new NotSupportedException(); // Unused
    protected override bool NotSupportedException(Task task, bool taskWasPreviouslyQueued) => throw new NotSupportedException(); // Unused
}

다음과 같이 작업을 시작하십시오.

var scheduler = new SingleThreadTaskScheduler();
var task = Task.Factory.StartNew(action, cancellationToken, TaskCreationOptions.LongRunning, scheduler);

나중에 다음과 같이 중단 할 수 있습니다.

scheduler.TaskThread.Abort();

점을 유의 스레드를 중단에 대한 경고가 계속 적용 :

Thread.Abort방법은주의해서 사용해야합니다. 특히 현재 스레드 이외의 스레드를 중단하기 위해 호출 할 때 ThreadAbortException 이 발생 했을 때 어떤 코드가 실행되었거나 실행되지 않았는지 알 수 없으며 응용 프로그램의 상태 또는 응용 프로그램 및 사용자 상태를 확실하게 알 수 없습니다 보존 책임이 있습니다. 예를 들어 호출 Thread.Abort하면 정적 생성자가 실행되지 못하게하거나 관리되지 않는 리소스가 해제되지 않을 수 있습니다.


이 코드는 런타임 예외와 함께 실패합니다. System.InvalidOperationException : 이미 시작된 작업에서 RunSynchronously가 호출되지 않을 수 있습니다.
Theodor Zoulias

1
@TheodorZoulias 잘 잡았습니다. 감사. 코드를 수정하고 일반적으로 답변을 개선했습니다.
Edward Brey

1
그래, 이것은 버그를 수정했다. 아마도 언급해야 할 또 다른 경고는 Thread.Abort.NET Core에서 지원되지 않는다는 것입니다 . 이를 사용하려고하면 예외가 발생합니다. System.PlatformNotSupportedException :이 플랫폼에서는 스레드 중단이 지원되지 않습니다. 세 번째 경고는 SingleThreadTaskScheduler약속 스타일 작업, 즉 async대의원으로 만든 작업에서 효과적으로 사용할 수 없다는 것 입니다. 예를 들어 임베디드 await Task.Delay(1000)는 스레드없이 실행되므로 스레드 이벤트에는 영향을 미치지 않습니다.
Theodor Zoulias
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.