.NET에서 스레드를 깨끗하게 종료하는 것에 대한 질문
나는 Thread.Abort ()가 주제에 대해 읽은 많은 기사에서 악하다는 것을 이해하므로 현재는 더 깨끗한 방법으로 대체하기 위해 내 중단 항목에서 추출하는 과정에 있습니다. 여기 stackoverflow에서 사람들의 사용자 전략을 비교 한 다음 MSDN에서 " How to : Create and Terminate Threads (C # Programming Guide) " 를 읽은 후 volatile bool
접근 방식 확인 전략 을 사용하는 접근 방식 을 거의 동일하게 설명합니다. 좋지만 몇 가지 질문이 있습니다 ....
여기서 제가 눈에 띄는 것은 바로 크 런칭 코드 루프를 실행하는 간단한 작업자 프로세스가 없다면 어떨까요? 예를 들어, 제 프로세스는 백그라운드 파일 업 로더 프로세스입니다. 사실 각 파일을 통해 루프를 수행하므로 while (!_shouldStop)
모든 루프 반복을 처리하는 맨 위에 내 프로세스를 추가 할 수 있지만 더 많은 비즈니스 프로세스가 있습니다. 다음 루프 반복에 도달하기 전에 발생하는이 취소 절차는 간단합니다. 전체 작업자 함수에 걸쳐 4-5 줄마다 이러한 while 루프를 뿌려야한다고 말하지 마십시오.
나는 정말로 더 나은 방법이 있기를 바랍니다. 이것이 사실인지, 올바른 [유일한?] 접근 방법인지, 아니면 과거에 내가 추구하는 것을 달성하기 위해 사용한 전략인지에 대해 누군가 제게 조언 해 주시겠습니까?
고마워 갱.
추가 읽기 : 이러한 모든 SO 응답 은 작업자 스레드가 반복 될 것이라고 가정합니다. 그것은 나에게 편안하게 앉아 있지 않습니다. 선형 적이지만시기 적절한 백그라운드 작업이라면 어떨까요?
불행히도 더 나은 옵션이 없을 수 있습니다. 특정 시나리오에 따라 다릅니다. 아이디어는 안전한 지점에서 스레드를 우아하게 중지하는 것입니다. 그것이 Thread.Abort
좋지 않은 이유의 핵심입니다 . 안전한 지점에서 발생하는 것이 보장되지 않기 때문입니다. 중지 메커니즘으로 코드를 뿌림으로써 안전 지점을 효과적으로 수동으로 정의 할 수 있습니다. 이를 협력 취소라고합니다. 이를 수행하기위한 기본적으로 4 가지 광범위한 메커니즘이 있습니다. 상황에 가장 잘 맞는 것을 선택할 수 있습니다.
중지 플래그 폴링
이미이 방법을 언급하셨습니다. 이것은 매우 일반적인 것입니다. 알고리즘의 안전한 지점에서 주기적으로 플래그를 확인하고 신호를 받으면 구제 조치를 취하십시오. 표준 접근 방식은 변수를 표시하는 것입니다 volatile
. 가능하지 않거나 불편한 경우 lock
. volatile
예를 들어 람다식이 클로저를 통해 캡처하는 경우 로컬 변수를 표시 할 수 없다는 점을 기억하십시오 . 필요한 메모리 장벽을 생성하는 다른 방법을 사용해야합니다. 이 방법에 대해 말해야 할 다른 것은 없습니다.
TPL에서 새로운 취소 메커니즘 사용
이는 TPL에서 새로운 취소 데이터 구조를 사용한다는 점을 제외하면 중지 플래그를 폴링하는 것과 유사합니다. 이는 여전히 협력 적 취소 패턴을 기반으로합니다. 당신은 a CancellationToken
와 주기적으로 확인해야 IsCancellationRequested
합니다. 취소를 요청하려면 원래 토큰을 제공 한을 호출 Cancel
해야합니다 CancellationTokenSource
. 새로운 취소 메커니즘으로 할 수있는 일이 많이 있습니다. 여기에서 자세한 내용을 읽을 수 있습니다 .
대기 핸들 사용
이 방법은 작업자 스레드가 특정 간격 또는 정상 작동 중에 신호를 기다려야하는 경우 유용 할 수 있습니다. 당신은 할 수 , 예를 들어, 스레드가 정지하는 시간을 알려합니다. 이벤트가 신호를 받았는지 여부를 나타내는를 반환하는 함수를 사용하여 이벤트를 테스트 할 수 있습니다 . 는 이벤트가 시간의 양에 신호되지 않은 경우 지정이 얼마나 많은 시간 반환에 전화를 기다릴하는 매개 변수를 사용합니다. 이 기술을 대신 사용 하고 동시에 중지 표시를 얻을 수 있습니다. 스레드가 기다려야하는 다른 인스턴스가있는 경우에도 유용합니다 . 한 번의 호출로 모든 이벤트 (중지 이벤트 포함)를 기다리도록 호출 할 수 있습니다 . 이벤트를 사용하는 것이 전화하는 것보다 낫습니다.Set
ManualResetEvent
WaitOne
bool
WaitOne
Thread.Sleep
WaitHandle
WaitHandle.WaitAny
Thread.Interrupt
프로그램의 흐름을 더 잘 제어 할 수 있기 때문에 ( Thread.Interrupt
예외가 발생하므로 try-catch
필요한 정리를 수행하기 위해 전략적으로 블록을 배치 해야합니다).
전문 시나리오
매우 특수한 중지 메커니즘이있는 몇 가지 일회성 시나리오가 있습니다. 그것들을 모두 열거하는 것은 분명히이 답변의 범위를 벗어납니다 (거의 불가능할 것이라는 점에 유의하지 마십시오). 여기서 제가 의미하는 바의 좋은 예는 Socket
수업입니다. 스레드가에 대한 호출에서 차단 된 경우 Send
또는 Receive
호출 Close
은 효과적으로 차단을 해제하는 모든 차단 호출에서 소켓을 인터럽트합니다. BCL에는 유사한 기술을 사용하여 스레드 차단을 해제 할 수있는 몇 가지 다른 영역이 있다고 확신합니다.
다음을 통해 스레드 중단 Thread.Interrupt
여기서의 장점은 간단하고 코드에 실제로 아무것도 뿌릴 필요가 없다는 것입니다. 단점은 알고리즘에서 안전 지점의 위치를 거의 제어 할 수 없다는 것입니다. 그 이유는 Thread.Interrupt
미리 준비된 BCL 차단 호출 중 하나에 예외를 삽입하여 작동 하기 때문 입니다. 이들은 다음을 포함한다 Thread.Sleep
, WaitHandle.WaitOne
, Thread.Join
당신은 당신이 그들을 배치 위치에 대한 현명 할 필요가 그래서, 등. 그러나 대부분의 시간은 알고리즘이 어디로 가는지 지시하며 특히 알고리즘이 이러한 차단 호출 중 하나에 대부분의 시간을 소비하는 경우에는 일반적으로 괜찮습니다. 알고리즘이 BCL에서 차단 호출 중 하나를 사용하지 않으면이 방법이 작동하지 않습니다. 여기에 이론은이 있다는 것입니다 ThreadInterruptException
그것은 그래서에만 .NET 통화 중 대기에서 생성 가능성안전한 지점에서. 최소한 스레드가 관리되지 않는 코드에 있거나 획득 된 상태에 매달려있는 잠금을 남겨 두는 중요 섹션에서 벗어날 수 없다는 것을 알고 있습니다. 이것이 Thread.Abort
어느 호출에 응답하는지 명확하지 않고 많은 개발자가 그 뉘앙스에 익숙하지 않기 때문에 여전히 사용을 권장하지 않습니다.
글쎄, 안타깝게도 멀티 스레딩에서는 청결을 위해 "snappiness"를 타협해야하는 경우가 종종 있습니다. 스레드를 즉시 종료 할 수는 Interrupt
있지만 매우 깨끗하지는 않습니다. 따라서 _shouldStop
4 ~ 5 줄마다 검사 를 뿌릴 필요는 없지만 스레드를 중단하면 예외를 처리하고 깨끗한 방식으로 루프를 종료해야합니다.
최신 정보
루핑 스레드가 아니더라도 (예 : 장기 실행 비동기 작업이나 입력 작업을위한 블록 유형을 수행하는 스레드 일 수 Interrupt
있음) 가능하지만 여전히 ThreadInterruptedException
스레드를 잡아서 깔끔하게 종료해야합니다. 지금까지 읽은 예가 매우 적절하다고 생각합니다.
2.0 업데이트
예, 예가 있습니다 ... 참조한 링크를 기반으로 한 예를 보여 드리겠습니다.
public class InterruptExample
{
private Thread t;
private volatile boolean alive;
public InterruptExample()
{
alive = false;
t = new Thread(()=>
{
try
{
while (alive)
{
/* Do work. */
}
}
catch (ThreadInterruptedException exception)
{
/* Clean up. */
}
});
t.IsBackground = true;
}
public void Start()
{
alive = true;
t.Start();
}
public void Kill(int timeout = 0)
{
// somebody tells you to stop the thread
t.Interrupt();
// Optionally you can block the caller
// by making them wait until the thread exits.
// If they leave the default timeout,
// then they will not wait at all
t.Join(timeout);
}
}
취소가 구축중인 항목의 요구 사항 인 경우 나머지 코드만큼 존중하여 처리해야합니다. 디자인해야 할 수도 있습니다.
스레드가 항상 두 가지 작업 중 하나를 수행한다고 가정합니다.
- CPU 바운드
- 커널을 기다리는 중
문제의 스레드에 CPU가 묶여 있다면, 아마도 구제 금융 체크를 삽입 할 좋은 자리가 있을 것입니다 . 오래 실행되는 CPU 바운드 작업을 수행하기 위해 다른 사람의 코드를 호출하는 경우 외부 코드를 수정하고 프로세스 밖으로 이동해야 할 수 있습니다 (스레드를 중단하는 것은 좋지 않지만 프로세스를 중단하는 것은 잘 정의되고 안전합니다. ) 등
커널을 기다리고 있다면 대기에 관련된 핸들 (또는 fd 또는 mach 포트 등)이있을 것입니다. 일반적으로 관련 핸들을 파괴하면 커널이 즉시 오류 코드와 함께 반환됩니다. .net / java / etc에있는 경우. 예외로 끝날 것입니다. C에서 시스템 호출 실패를 처리하기 위해 이미 배치 된 코드는 앱의 의미있는 부분까지 오류를 전파합니다. 어느 쪽이든, 새 코드를 사방에 뿌릴 필요없이 매우 깔끔하고시기 적절한 방식으로 낮은 수준의 장소에서 벗어날 수 있습니다.
이런 종류의 코드에서 자주 사용하는 전술은 닫아야하는 핸들 목록을 추적 한 다음 중단 함수가 "취소됨"플래그를 설정 한 다음 닫는 것입니다. 함수가 실패하면 특정 예외 / errno가 무엇이든간에 플래그를 확인하고 취소로 인한 실패를보고 할 수 있습니다.
취소에 대해 허용 가능한 세분성이 서비스 요청 수준이라는 것을 암시하는 것 같습니다. 이것은 아마도 좋은 생각이 아닙니다. 백그라운드 작업을 동 기적으로 취소하고 포 그라운드 스레드에서 이전 백그라운드 스레드를 결합하는 것이 훨씬 낫습니다. 다음과 같은 이유로 더 깨끗합니다.
예상치 못한 지연 후 오래된 bgwork 스레드가 다시 살아날 때 일종의 경쟁 조건을 피합니다.
중단 된 백그라운드 스레드의 효과를 숨길 수 있도록하여 중단 된 백그라운드 프로세스로 인한 잠재적 인 숨겨진 스레드 / 메모리 누수를 방지합니다.
이 접근 방식을 두려워하는 두 가지 이유가 있습니다.
적시에 자신의 코드를 중단 할 수 없다고 생각합니다. 취소가 앱의 요구 사항 인 경우 정말 필요한 결정은 리소스 / 비즈니스 결정입니다. 해킹을하거나 문제를 깔끔하게 수정합니다.
제어 할 수 없기 때문에 호출하는 일부 코드를 신뢰하지 않습니다. 정말로 신뢰하지 않는다면 프로세스 외부로 이동하는 것이 좋습니다. 이런 식으로 이것을 포함하여 여러 종류의 위험으로부터 훨씬 더 잘 격리됩니다.
아마도 문제의 한 부분은 당신이 그렇게 긴 메소드 / while 루프를 가지고 있다는 것입니다. 스레딩 문제가 있는지 여부에 관계없이 더 작은 처리 단계로 나누어야합니다. 이러한 단계가 Alpha (), Bravo (), Charlie () 및 Delta ()라고 가정 해 보겠습니다.
그런 다음 다음과 같이 할 수 있습니다.
public void MyBigBackgroundTask()
{
Action[] tasks = new Action[] { Alpha, Bravo, Charlie, Delta };
int workStepSize = 0;
while (!_shouldStop)
{
tasks[workStepSize++]();
workStepSize %= tasks.Length;
};
}
예, 끝없이 반복되지만 각 비즈니스 단계 사이에 멈출 시간인지 확인합니다.
모든 곳에 while 루프를 뿌릴 필요가 없습니다. 외부 while 루프는 중지하라는 메시지가 표시되었는지 확인하고 중지하라는 메시지가 추가로 반복되지 않는지 확인합니다.
만약 당신이 직선적 인 "무언가를하고 닫는"쓰레드 (루프가 없음)를 가지고 있다면 당신은 쓰레드 내부의 각 주요 지점 앞이나 뒤에 _shouldStop 부울을 확인합니다. 그래야 계속 진행해야하는지 아니면 구제 조치를 취해야하는지 알 수 있습니다.
예를 들면 :
public void DoWork() {
RunSomeBigMethod();
if (_shouldStop){ return; }
RunSomeOtherBigMethod();
if (_shouldStop){ return; }
//....
}
The best answer largely depends on what you're doing in the thread.
Like you said, most answers revolve around polling a shared boolean every couple lines. Even though you may not like it, this is often the simplest scheme. If you want to make your life easier, you can write a method like ThrowIfCancelled(), which throws some kind of exception if you're done. The purists will say this is (gasp) using exceptions for control flow, but then again cacelling is exceptional imo.
If you're doing IO operations (like network stuff), you may want to consider doing everything using async operations.
If you're doing a sequence of steps, you could use the IEnumerable trick to make a state machine. Example:
<
abstract class StateMachine : IDisposable
{
public abstract IEnumerable<object> Main();
public virtual void Dispose()
{
/// ... override with free-ing code ...
}
bool wasCancelled;
public bool Cancel()
{
// ... set wasCancelled using locking scheme of choice ...
}
public Thread Run()
{
var thread = new Thread(() =>
{
try
{
if(wasCancelled) return;
foreach(var x in Main())
{
if(wasCancelled) return;
}
}
finally { Dispose(); }
});
thread.Start()
}
}
class MyStateMachine : StateMachine
{
public override IEnumerabl<object> Main()
{
DoSomething();
yield return null;
DoSomethingElse();
yield return null;
}
}
// then call new MyStateMachine().Run() to run.
>
Overengineering? It depends how many state machines you use. If you just have 1, yes. If you have 100, then maybe not. Too tricky? Well, it depends. Another bonus of this approach is that it lets you (with minor modifications) move your operation into a Timer.tick callback and void threading altogether if it makes sense.
and do everything that blucz says too.
Instead of adding a while loop where a loop doesn't otherwise belong, add something like if (_shouldStop) CleanupAndExit();
wherever it makes sense to do so. There's no need to check after every single operation or sprinkle the code all over with them. Instead, think of each check as a chance to exit the thread at that point and add them strategically with this in mind.
All these SO responses assume the worker thread will loop. That doesn't sit comfortably with me
There are not a lot of ways to make code take a long time. Looping is a pretty essential programming construct. Making code take a long time without looping takes a huge amount of statements. Hundreds of thousands.
Or calling some other code that is doing the looping for you. Yes, hard to make that code stop on demand. That just doesn't work.
참고URL : https://stackoverflow.com/questions/3632149/question-about-terminating-a-thread-cleanly-in-net
'Programing' 카테고리의 다른 글
MySQL : INT를 DATETIME으로 변환 (0) | 2020.12.11 |
---|---|
표 셀에서 링크를 만들 수있는 방법 (0) | 2020.12.11 |
멀티 코어 시스템에 대한 작업 (-j) 플래그를 자동으로 설정 하시겠습니까? (0) | 2020.12.11 |
hazelcast 대 ehcache (0) | 2020.12.11 |
2 바이트 배열을 결합하는 방법 (0) | 2020.12.11 |