SynchronizationContext의 기능은 무엇입니까?


135

Programming C # 책에는 다음에 대한 샘플 코드가 있습니다 SynchronizationContext.

SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
    string text = File.ReadAllText(@"c:\temp\log.txt");
    originalContext.Post(delegate {
        myTextBox.Text = text;
    }, null);
});

저는 스레드 초보자입니다. 자세히 답변 해주세요. 첫째, 문맥이 무엇을 의미하는지, 프로그램이 무엇을 저장 originalContext합니까? 그리고 Post메소드가 시작되면 UI 스레드는 어떻게됩니까?
내가 어리석은 것들을 물어 보면 고마워주세요.

편집 : 예를 들어 방금 myTextBox.Text = text;메서드에 쓰면 어떻게 다릅니 까?


1
이 매뉴얼에 의해 구현 된 동기화 모델의 목적은 공용 언어 런타임의 내부 비동기 / 동기화 작업이 다른 동기화 모델과 올바르게 작동하도록하는 것입니다. 이 모델은 또한 다른 동기화 환경에서 올바르게 작동하기 위해 관리되는 응용 프로그램이 따라야하는 일부 요구 사항을 단순화합니다.
ta.speot.is는

IMHO 비동기 대기 이미 이미
Royi Namir

7
@RoyiNamir : 예,하지만 추측 async/ await에 의존 SynchronizationContext아래에.
stakx-더 이상

답변:


170

SynchronizationContext의 기능은 무엇입니까?

간단히 말해서 SynchronizationContext코드가 실행될 수있는 위치를 나타냅니다. 그런 다음 해당 위치 Send또는 Post메소드 로 전달 된 대리인이 해당 위치에서 호출됩니다. ( Post의 비 차단 / 비동기 버전입니다 Send.)

모든 스레드는 SynchronizationContext연관된 인스턴스 를 가질 수 있습니다 . 정적 SynchronizationContext.SetSynchronizationContext메소드 를 호출하여 실행중인 스레드를 동기화 컨텍스트와 연관시킬 수 있으며 실행중인 스레드 의 현재 컨텍스트를 SynchronizationContext.Current특성을 통해 조회 할 수 있습니다 .

방금 쓴 내용에도 불구하고 (각 스레드는 관련 동기화 컨텍스트를 가짐) SynchronizationContext반드시 특정 스레드를 나타내는 것은 아닙니다 . 또한 전달 된 델리게이트의 호출을 여러 스레드 (예 : ThreadPool작업자 스레드) 또는 (적어도 이론적으로는) 특정 CPU 코어 또는 다른 네트워크 호스트로 전달할 수 있습니다 . 대리인이 실행되는 위치는 SynchronizationContext사용 된 유형에 따라 다릅니다 .

Windows Forms는 WindowsFormsSynchronizationContext첫 번째 양식이 작성된 스레드에를 설치합니다 . 이 스레드는 일반적으로 "UI 스레드"라고합니다.이 유형의 동기화 컨텍스트는 해당 스레드에 전달 된 대리자를 호출합니다. Windows Forms는 다른 많은 UI 프레임 워크와 마찬가지로 컨트롤이 만들어진 동일한 스레드에서만 컨트롤을 조작 할 수 있기 때문에 매우 유용합니다.

방금 myTextBox.Text = text;메서드에 쓰면 어떻게 되나요?

전달한 코드 ThreadPool.QueueUserWorkItem는 스레드 풀 작업자 스레드에서 실행됩니다. 즉, myTextBox작성된 스레드에서 실행되지 않으므로 Windows Forms는 조만간 (특히 릴리스 빌드에서) 예외를 발생시켜 myTextBox다른 스레드에서 액세스 할 수 없음을 알려줍니다 .

그렇기 때문에 myTextBox특정 할당 전에 작업자 스레드에서 "UI 스레드"( 생성 된 위치) 로 "전환" 해야합니다. 이것은 다음과 같이 수행됩니다.

  1. 여전히 UI 스레드에있는 동안 Windows Forms를 캡처 하고 나중에 사용할 수 있도록 SynchronizationContext변수에 참조를 저장하십시오 ( originalContext). SynchronizationContext.Current이 시점에서 쿼리해야합니다 . 에 전달 된 코드 내에서 쿼리 ThreadPool.QueueUserWorkItem하면 스레드 풀의 작업자 스레드와 관련된 동기화 컨텍스트가 무엇이든 얻을 수 있습니다. Windows Forms의 컨텍스트에 대한 참조를 저장 한 후에는 언제 어디서나 UI 스레드에 코드를 "전송"할 수 있습니다.

  2. UI 요소를 조작해야 할 때마다 (더 이상 UI 스레드에 있지 않거나 그렇지 않을 수도 있음)을 통해 Windows Forms의 동기화 컨텍스트에 액세스 originalContext하고 UI를 조작하는 코드를 Send또는로 전달하십시오 Post.


최종 발언 및 힌트 :

  • 동기화 컨텍스트 수행하지 않는 것은 특정 위치 / 컨텍스트에서 실행해야하는 코드와 코드를에 전달하지 않고 정상적으로 실행할 수있는 코드를 알려주는 것 SynchronizationContext입니다. 이를 결정하려면 프로그래밍중인 프레임 워크의 규칙과 요구 사항 (이 경우 Windows Forms)을 알아야합니다.

    따라서 Windows Forms에 대한이 간단한 규칙을 기억하십시오. 컨트롤이나 폼을 만든 스레드 이외의 스레드에서 컨트롤이나 폼에 액세스하지 마십시오. 이 작업을 수행해야하는 경우 SynchronizationContext위에서 설명한 메커니즘을 사용 하거나 Control.BeginInvoke정확히 동일한 작업을 수행하는 Windows Forms 관련 방법입니다.

  • 있는 거 프로그래밍 .NET 4.5에 대한 이상이있는 경우, 당신은 명시 적으로 코드를 변환하여 당신의 인생을 훨씬 쉽게 만들 수 있습니다 사용 SynchronizationContext, ThreadPool.QueueUserWorkItem, control.BeginInvoke, 등을 통해 새에 async/의 await키워드작업 병렬 라이브러리 (TPL) 즉, 주변의 API, TaskTask<TResult>클래스. 이들은 상당히 높은 수준으로 UI 스레드의 동기화 컨텍스트를 캡처하고 비동기 작업을 시작한 다음 UI 스레드로 돌아가서 작업 결과를 처리 할 수 ​​있도록합니다.


당신은 말을 윈도우 폼은, 많은 다른 UI 프레임 워크처럼 만 동일한 스레드에서 컨트롤의 조작을 허용 하지만, Windows의 모든 창은 그것을 만든 동일한 스레드에서 액세스해야합니다.
user34660

4
@ user34660 : 아니요, 맞지 않습니다. Windows Forms 컨트롤을 만드는 여러 스레드가있을 수 있습니다 . 그러나 각 컨트롤은 해당 컨트롤을 만든 하나의 스레드와 연결되며 해당 스레드 하나만 액세스해야합니다. 서로 다른 UI 스레드의 컨트롤은 서로 상호 작용하는 방식이 매우 제한되어 있습니다. 하나는 다른 쪽의 부모 / 자식 일 수 없으며, 그 사이의 데이터 바인딩이 불가능합니다. 마지막으로, 컨트롤을 만드는 각 스레드에는 고유 한 메시지가 필요합니다. 루프 ( Application.RunIIRC로 시작 ). 이것은 상당히 발전된 주제이며 우연히 수행 된 것이 아닙니다.
stakx-더 이상

내 첫 번째 의견은 "다른 많은 UI 프레임 워크와 마찬가지로"이라고 말했기 때문에 일부 창 은 다른 스레드 에서 "제어 조작"을 허용 하지만 Windows 창은 허용하지 않음을 의미합니다. 같은 창에 대해 "Windows Forms 컨트롤을 만드는 여러 개의 스레드"를 가질 수 없으며 " 같은 스레드로 액세스해야합니다"와 "한 스레드에서만 액세스해야합니다"는 같은 말을합니다. 같은 창에 대해 "다른 UI 스레드에서 컨트롤"을 만들 수 있는지 의심됩니다. 이 모든 것이 .Net 이전의 Windows 프로그래밍 경험이있는 사용자에게는 고급 기능이 아닙니다.
user34660

3
"windows"와 "Windows windows"에 대한이 모든 이야기는 나를 어지럽게 만듭니다. 이 "창"에 대해 언급 했습니까? 나는 그렇게 생각하지 않습니다 ...
stakx-더 이상

1
@ibubi : 귀하의 질문을 이해하지 못했습니다. 스레드의 동기화 컨텍스트가 설정되지 않았 null거나 ( ) 인스턴스 SynchronizationContext(또는 그 하위 클래스)입니다. 그 인용의 요점은 당신이 얻는 것이 아니라 얻을 수 없는 것입니다 : UI 스레드의 동기화 컨텍스트.
stakx-더 이상

24

다른 답변에 추가하고 싶습니다 SynchronizationContext.Post. 대상 스레드에서 나중에 실행하기 위해 콜백을 대기열에 넣고 (일반적으로 대상 스레드 메시지 루프의 다음주기 동안) 호출 스레드에서 실행이 계속됩니다. 반면 SynchronizationContext.Send에 대상 스레드에서 콜백을 즉시 실행하려고하면 호출 스레드가 차단되고 교착 상태가 발생할 수 있습니다. 두 경우 모두 코드 재진입 가능성이 있습니다 (동일한 메소드에 대한 이전 호출이 리턴되기 전에 동일한 실행 스레드에서 클래스 메소드를 입력 할 수 있음).

Win32 프로그래밍 모델에 익숙하다면 API PostMessageSendMessageAPI 가 매우 유사합니다. API는 대상 창의 스레드와 다른 스레드에서 메시지를 발송하도록 호출 할 수 있습니다.

동기화 컨텍스트에 대한 설명은 다음과 같습니다 . SynchronizationContext에 관한 모든 것 입니다.


16

SynchronizationContext에서 파생 된 클래스 인 동기화 공급자를 저장합니다. 이 경우 아마도 WindowsFormsSynchronizationContext의 인스턴스 일 것입니다. 이 클래스는 Control.Invoke () 및 Control.BeginInvoke () 메서드를 사용하여 Send () 및 Post () 메서드를 구현합니다. 또는 DispatcherSynchronizationContext 일 수 있으며 Dispatcher.Invoke () 및 BeginInvoke ()를 사용합니다. Winforms 또는 WPF 앱에서 창을 생성하자마자 해당 공급자가 자동으로 설치됩니다.

스 니펫에 사용 된 스레드 풀 스레드와 같은 다른 스레드에서 코드를 실행할 때는 스레드 안전하지 않은 객체를 직접 사용하지 않도록주의해야합니다. 다른 사용자 인터페이스 개체와 마찬가지로 TextBox를 만든 스레드에서 TextBox.Text 속성을 업데이트해야합니다. Post () 메서드는 대리자 대상이 해당 스레드에서 실행되도록합니다.

이 스 니펫은 약간 위험하므로 UI ​​스레드에서 호출 할 때만 제대로 작동합니다. SynchronizationContext.Current는 스레드마다 값이 다릅니다. UI 스레드 만 사용 가능한 값을 갖습니다. 그리고 코드가 복사해야하는 이유입니다. Winforms 앱에서보다 읽기 쉽고 안전한 방법 :

    ThreadPool.QueueUserWorkItem(delegate {
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.BeginInvoke(new Action(() => {
            myTextBox.Text = text;
        }));
    });

어떤에서 호출 할 때 작동하는 장점이 있는 스레드를. SynchronizationContext.Current 사용의 장점은 코드가 Winforms에서 사용되는지 WPF에서 사용되는지에 관계없이 라이브러리에서 중요하다는 것입니다. 이것은 확실히 그러한 코드의 좋은 예는 아니며 , 항상 여기에 어떤 종류의 TextBox가 있는지 알고 있으므로 Control.BeginInvoke 또는 Dispatcher.BeginInvoke를 사용할지 항상 알 수 있습니다. 실제로 SynchronizationContext.Current를 사용하는 것은 그리 일반적이지 않습니다.

이 책은 스레딩에 대해 가르치려고 하므로이 결함이있는 예제를 사용하는 것이 좋습니다. 실제로 SynchronizationContext.Current 사용을 고려할 있는 몇 가지 경우에는 여전히 C #의 async / await 키워드 또는 TaskScheduler.FromCurrentSynchronizationContext ()까지 그대로 두십시오. 그러나 똑같은 이유로 스 니펫을 잘못된 스레드에서 사용할 때 여전히 스 니펫이 수행하는 방식이 잘못 작동합니다. 여기에서 매우 일반적인 질문 인 추가 추상화 수준은 유용하지만 왜 제대로 작동하지 않는지 알아 내기 어렵습니다. 바라건대 책은 그것을 사용하지 않을 때도 알려줍니다 :)


죄송합니다. 왜 UI 스레드 핸들이 스레드로부터 안전합니까? 즉, Post ()가 실행될 때 UI 스레드가 myTextBox를 사용할 수 있다고 생각합니까?
cloudyFan

4
영어를 해독하기가 어렵습니다. 원래 스 니펫은 UI 스레드에서 호출 될 때만 올바르게 작동합니다. 이것은 매우 일반적인 경우입니다. 그런 다음에 만 UI 스레드에 다시 게시됩니다. 작업자 스레드에서 호출 된 경우 Post () 대리자 대상이 스레드 풀 스레드에서 실행됩니다. 카붐. 이것은 당신이 직접 시도하고 싶은 것입니다. 스레드를 시작하고 스레드가이 코드를 호출하도록합니다. 코드가 NullReferenceException으로 충돌하면 올바르게 수행했습니다.
Hans Passant

5

여기서 동기화 컨텍스트의 목적은 myTextbox.Text = text;기본 UI 스레드에서 호출되도록하는 것입니다.

Windows에서는 GUI 제어가 작성된 스레드에 의해서만 GUI 제어에 액세스해야합니다. 먼저 동기화하지 않고 백그라운드 스레드에서 텍스트를 할당하려고 시도하면 (이 패턴 또는 호출 패턴과 같은 여러 방법을 통해) 예외가 발생합니다.

이것이하는 일은 백그라운드 스레드를 만들기 전에 동기화 컨텍스트를 저장 한 다음 백그라운드 스레드는 컨텍스트를 사용하는 것입니다 .Post 메소드는 GUI 코드를 실행합니다.

예, 표시된 코드는 기본적으로 쓸모가 없습니다. 백그라운드 스레드를 작성하는 이유는 즉시 기본 UI 스레드로 돌아 가야하는 이유는 무엇입니까? 단지 예일뿐입니다.


4
"예, 보여 드린 코드는 기본적으로 쓸모가 없습니다. 왜 백그라운드 스레드를 생성해야합니까? 메인 UI 스레드로 즉시 돌아 가면 되나요?" -파일이 크면 파일을 읽는 것이 긴 작업 일 수 있습니다. UI 스레드를 차단하고 응답하지 않을 수 있습니다.
Yair Nevet

바보 같은 질문이 있습니다. 모든 스레드에는 ID가 있으며 UI 스레드에도 ID = 2가 있다고 가정합니다. 그런 다음 스레드 풀 스레드에있을 때 다음과 같은 작업을 수행 할 수 있습니까? var thread = GetThread (2); thread.Execute (() => textbox1.Text = "foo")?
John

@ John-아니요, 스레드가 이미 실행 중이기 때문에 작동하지 않는다고 생각합니다. 이미 실행중인 스레드를 실행할 수 없습니다. 스레드가 (IIRC)를 실행하지 않을 때 실행에만 작동
에릭 Funkenbusch

3

소스로

모든 스레드에는 이와 관련된 컨텍스트 ( "현재"컨텍스트라고도 함)가 있으며 이러한 컨텍스트는 스레드간에 공유 될 수 있습니다. ExecutionContext에는 현재 환경 또는 프로그램이 실행중인 컨텍스트의 관련 메타 데이터가 포함됩니다. SynchronizationContext는 추상화를 나타냅니다. 응용 프로그램 코드가 실행되는 위치를 나타냅니다.

SynchronizationContext를 사용하면 작업을 다른 컨텍스트에 대기시킬 수 있습니다. 모든 스레드는 자체 SynchronizatonContext를 가질 수 있습니다.

예를 들어, Thread1과 Thread2라는 두 개의 스레드가 있다고 가정하십시오. Thread1이 약간의 작업을 수행하고 있으며 Thread1이 Thread2에서 코드를 실행하려고합니다. 가능한 한 가지 방법은 Thread2에 SynchronizationContext 객체를 요청하고 Thread1에 제공 한 다음 Thread1은 SynchronizationContext.Send를 호출하여 Thread2에서 코드를 실행할 수 있습니다.


2
동기화 컨텍스트가 특정 스레드에 반드시 연결되어있는 것은 아닙니다. 여러 스레드가 단일 동기화 컨텍스트에 대한 요청을 처리하고 단일 스레드가 여러 동기화 컨텍스트에 대한 요청을 처리 할 수 ​​있습니다.
Servy

3

SynchronizationContext는 다른 스레드에서 UI를 업데이트하는 방법을 제공합니다 (Send 메소드를 통해 또는 Post 메소드를 통해 비동기 적으로).

다음 예를 살펴보십시오.

    private void SynchronizationContext SyncContext = SynchronizationContext.Current;
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Thread thread = new Thread(Work1);
        thread.Start(SyncContext);
    }

    private void Work1(object state)
    {
        SynchronizationContext syncContext = state as SynchronizationContext;
        syncContext.Post(UpdateTextBox, syncContext);
    }

    private void UpdateTextBox(object state)
    {
        Thread.Sleep(1000);
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.Text = text;
    }

SynchronizationContext.Current는 UI 스레드의 동기화 컨텍스트를 반환합니다. 이것을 어떻게 알 수 있습니까? 모든 폼 또는 WPF 앱이 시작될 때 컨텍스트는 UI 스레드에서 설정됩니다. WPF 앱을 만들고 예제를 실행하면 버튼을 클릭하면 약 1 초 동안 잠자고 파일 내용이 표시됩니다. UpdateTextBox 메소드 (Work1)의 호출자는 Thread에 전달 된 메소드이므로 기본 UI 스레드가 아닌 해당 스레드를 휴면해야합니다. Work1 메소드가 스레드로 전달 되더라도 SyncContext 인 오브젝트도 허용합니다. 살펴보면 UpdateTextBox 메서드가 Work1 메서드가 아니라 syncContext.Post 메서드를 통해 실행되는 것을 볼 수 있습니다. 다음을 살펴보십시오.

private void Button_Click(object sender, RoutedEventArgs e) 
{
    Thread.Sleep(1000);
    string text = File.ReadAllText(@"c:\temp\log.txt");
    myTextBox.Text = text;
}

마지막 예와이 예는 동일하게 실행됩니다. 둘 다 작업을 수행하는 동안 UI를 차단하지 않습니다.

결론적으로 SynchronizationContext를 스레드로 생각하십시오. 스레드가 아니며 스레드를 정의합니다 (모든 스레드에 SyncContext가있는 것은 아닙니다). UI를 업데이트하기 위해 Post 또는 Send 메서드를 호출 할 때마다 기본 UI 스레드에서 UI를 정상적으로 업데이트하는 것과 같습니다. 어떤 이유로 다른 스레드에서 UI를 업데이트해야하는 경우 스레드에 기본 UI 스레드의 SyncContext가 있는지 확인하고 실행하려는 메소드를 사용하여 Send 또는 Post 메소드를 호출하십시오. 세트.

이것이 당신에게 도움이되기를 바랍니다, 친구!


2

SynchronizationContext는 기본적으로 프로그램의 특정 부분 (.Net TPL의 Task obj에 캡슐화 됨) 이 실행을 완료 한 후 지정된 실행 컨텍스트에서 델리게이트가 실행되도록하는 콜백 델리게이트 실행 공급자입니다 .

기술적 인 관점에서 볼 때 SC는 작업 병렬 라이브러리 개체에 대한 기능을 지원하고 제공하기위한 간단한 C # 클래스입니다.

콘솔 응용 프로그램을 제외한 모든 .Net 응용 프로그램에는 특정 기본 프레임 워크 (예 : WPF, WindowsForm, Asp Net, Silverlight, ecc.)를 기반으로이 클래스의 특정 구현이 있습니다.

이 객체의 중요성은 비동기 코드 실행에서 반환되는 결과와 해당 비동기 작업의 결과를 기다리는 종속 코드 실행 간의 동기화에 있습니다.

"컨텍스트"라는 단어는 실행 컨텍스트, 즉 대기 코드가 실행될 현재 실행 컨텍스트, 즉 비동기 코드와 대기 코드 사이의 동기화가 특정 실행 컨텍스트에서 발생하므로이 개체를 SynchronizationContext라고합니다. 비동기 코드의 동기화 및 대기 코드 실행을 돌보는 실행 컨텍스트를 나타냅니다 .


1

이 예제는 Joseph Albahari의 Linqpad 예제에서 가져온 것이지만 동기화 컨텍스트의 기능을 이해하는 데 실제로 도움이됩니다.

void WaitForTwoSecondsAsync (Action continuation)
{
    continuation.Dump();
    var syncContext = AsyncOperationManager.SynchronizationContext;
    new Timer (_ => syncContext.Post (o => continuation(), _)).Change (2000, -1);
}

void Main()
{
    Util.CreateSynchronizationContext();
    ("Waiting on thread " + Thread.CurrentThread.ManagedThreadId).Dump();
    for (int i = 0; i < 10; i++)
        WaitForTwoSecondsAsync (() => ("Done on thread " + Thread.CurrentThread.ManagedThreadId).Dump());
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.