주어진 스레드에서 예외를 발생시키는 C#의 좋은 방법이 있습니까?

StackOverflow https://stackoverflow.com/questions/44656

  •  09-06-2019
  •  | 
  •  

문제

제가 작성하고 싶은 코드는 다음과 같습니다.

void MethodOnThreadA()
{
    for (;;)
    {
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    }
}

void MethodOnThreadB()
{
    try
    {
        for (;;)
        {
            // Do stuff
        }
    }
    catch (MyException ex)
    {
        // Do the right thing for this exception.
    }
}

스레드 B가 스레드 안전한 방식으로 주기적으로 스레드 A에 의해 플래그가 설정되었는지 확인하도록 할 수 있다는 것을 알고 있지만 이로 인해 코드가 더 복잡해집니다.사용할 수 있는 더 나은 메커니즘이 있습니까?

주기적으로 확인하는 좀 더 구체적인 예는 다음과 같습니다.

Dictionary<Thread, Exception> exceptionDictionary = new Dictionary<Thread, Exception>();

void ThrowOnThread(Thread thread, Exception ex)
{
    // the exception passed in is going to be handed off to another thread,
    // so it needs to be thread safe.
    lock (exceptionDictionary)
    {
        exceptionDictionary[thread] = ex;
    }
}

void ExceptionCheck()
{
    lock (exceptionDictionary)
    {
        Exception ex;
        if (exceptionDictionary.TryGetValue(Thread.CurrentThread, out ex))
            throw ex;
    }
}

void MethodOnThreadA()
{
    for (;;)
    {
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    }
}

void MethodOnThreadB()
{
    try
    {
        for (;;)
        {
            // Do stuff
            ExceptionCheck();
        }
    }
    catch (MyException ex)
    {
        // Do the right thing for this exception.
    }
}
도움이 되었습니까?

해결책

이것은 좋은 생각이 아니다

이 문서에서는 Ruby의 시간 초과 라이브러리에 대해 설명합니다. 스레드 전체에서 예외가 발생합니다.

그런 일을 하는 것이 어떻게 근본적으로 깨졌는지 설명합니다.이는 루비에서만 깨지는 것이 아니라 스레드 전체에서 예외가 발생하는 모든 곳에서 깨집니다.

간단히 말해서, 일어날 수 있는 일은 다음과 같습니다.

스레드A:

At some random time, throw an exception on thread B:

스레드B:

try {
    //do stuff
} finally {
    CloseResourceOne();
    // ThreadA's exception gets thrown NOW, in the middle 
    // of our finally block and resource two NEVER gets closed.
    // Obviously this is BAD, and the only way to stop is to NOT throw
    // exceptions across threads
    CloseResourceTwo();
}

실제로 스레드 전체에 예외를 발생시키지 않기 때문에 '주기적인 확인' 예는 괜찮습니다.
"다음에 이 플래그를 볼 때 예외 발생"이라는 플래그를 설정하는 것뿐입니다. 이는 "캐치 도중에 발생하거나 최종적으로 차단될 수 있음" 문제가 발생하지 않으므로 괜찮습니다.
그러나 그렇게 하려면 "exitnow" 플래그를 설정하고 이를 사용하면 예외 개체를 생성하는 번거로움을 덜 수 있습니다.휘발성 bool이 제대로 작동합니다.

다른 팁

스레드 중단 등과 같은 다른 메커니즘에 의해 스레드에 발생할 수 있는 예외에는 문제가 많으므로 다른 방법을 찾아야 합니다.

예외는 프로세스에서 처리할 수 없는 예외적인 상황이 발생했음을 알리는 데 사용되는 메커니즘입니다.예외가 발생했음을 알리는 데 사용되도록 코드 작성을 피해야 합니다. 다른 것 특별한 일을 경험했습니다.

다른 스레드는 예외를 처리하는 방법을 모를 가능성이 높습니다. 코드에 의해 발생할 수 있는 모든 경우.

즉, 예외를 사용하는 것보다 스레드를 중단하는 다른 메커니즘을 찾아야 합니다.

이벤트 객체나 이와 유사한 것을 사용하여 스레드에 처리를 중단하도록 지시하는 것이 가장 좋은 방법입니다.

다른 문제를 조사하는 동안 귀하의 질문이 생각나는 다음 기사를 발견했습니다.

Rotor를 사용하여 ThreadAbortException의 깊이 연결

이는 .NET이 Thread.Abort()를 구현하기 위해 거치는 회전을 보여줍니다. 아마도 다른 크로스 스레드 예외도 비슷해야 할 것입니다.(으윽!)

Orion Edwards가 말하는 내용은 전적으로 사실이 아닙니다."유일한" 방법은 아닙니다.

// Obviously this is BAD, and the only way to stop is to NOT throw
// exceptions across threads

CER 사용(제한된 실행 영역)을 C#에서 사용하면 리소스를 원자성 작업으로 해제하여 스레드 간 예외로부터 코드를 보호할 수 있습니다.이 기술은 릴리스되지 않은 핸들로 인해 메모리 누수가 발생할 수 있는 Windows의 기본 API와 작동하는 .NET Framework의 여러 클래스에서 사용됩니다.

보다 http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx

다음 예에서는 다음을 사용하여 안정적으로 핸들을 설정하는 방법을 보여줍니다. PrepareConstrainedRegions 방법.핸들을 지정된 기존 핸들로 안정적으로 설정하려면 기본 핸들 할당과 해당 핸들의 후속 기록이 SafeHandle 객체는 원자적입니다.이러한 작업(예: 스레드 중단 또는 메모리 부족 예외) 사이에 오류가 발생하면 기본 핸들이 유출됩니다.당신은 사용할 수 있습니다 PrepareConstrainedRegions 핸들이 새지 않는지 확인하는 방법.

다음과 같이 간단합니다.

public MySafeHandle AllocateHandle()
{
    // Allocate SafeHandle first to avoid failure later.
    MySafeHandle sh = new MySafeHandle();

    RuntimeHelpers.PrepareConstrainedRegions();
    try { }
    finally  // this finally block is atomic an uninterruptible by inter-thread exceptions
    {
        MyStruct myStruct = new MyStruct();
        NativeAllocateHandle(ref myStruct);
        sh.SetHandle(myStruct.m_outputHandle);
    }

    return sh;
}

왜 이 일을 하고 싶은지 알고 싶습니다.좋은 습관이 아니기 때문에 쉬운 방법은 없습니다.아마도 설계로 돌아가서 최종 목표를 달성하기 위한 더 깔끔한 방법을 찾아야 할 것입니다.

그건 좋은 생각이 아닌 것 같아요..이 문제를 해결하는 또 다른 방법 - 스레드 간 신호를 보내기 위해 공유 데이터와 같은 다른 메커니즘을 사용해 보십시오.

다른 사람들과 마찬가지로 그것이 좋은 생각인지는 잘 모르겠지만 정말로 그렇게 하고 싶다면 대상 스레드에 대리자를 게시하고 보낼 수 있는 SynchronizationContext의 하위 클래스를 만들 수 있습니다(WinForms 스레드인 경우 작업 해당 하위 클래스가 이미 존재하므로 이 작업이 수행됩니다.그러나 대상 스레드는 대리인을 수신하기 위해 일종의 메시지 펌프와 동등한 기능을 구현해야 합니다.

@오리온 에드워즈

finally 블록에서 예외가 발생한다는 점을 지적합니다.

그러나 나는 또 다른 스레드를 사용하여 이 예외를 인터럽트로 사용하는 방법이 있다고 생각합니다.

스레드 A:

At some random time, throw an exception on thread C:

스레드 B:

try {
    Signal thread C that exceptions may be thrown
    //do stuff, without needing to check exit conditions
    Signal thread C that exceptions may no longer be thrown
}
catch {
    // exception/interrupt occurred handle...
}
finally {
    // ...and clean up
    CloseResourceOne();
    CloseResourceTwo();
}

스레드 C:

 while(thread-B-wants-exceptions) {
        try {
            Thread.Sleep(1) 
        }
        catch {
            // exception was thrown...
            if Thread B still wants to handle exceptions
                throw-in-B
        }
    }

아니면 그냥 어리석은 짓인가요?

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