[C#] 교차 스레드 작업이 유효하지 않음 : 생성 된 스레드 이외의 스레드에서 액세스 한 제어

시나리오가 있습니다. (Windows Forms, C #, .NET)

  1. 일부 사용자 정의 컨트롤을 호스팅하는 기본 양식이 있습니다.
  2. 사용자 정의 컨트롤은 무거운 데이터 작업을 수행하므로 UserControl_Load메서드를 직접 호출하면 로드 메서드 실행 기간 동안 UI가 응답 하지 않습니다 .
  3. 이것을 극복하기 위해 다른 스레드에 데이터를로드합니다 (기존 코드를 가능한 한 적게 변경하려고 시도 함)
  4. 데이터를로드 할 백그라운드 작업자 스레드를 사용했으며 완료되면 응용 프로그램에 작업이 완료되었음을 알립니다.
  5. 이제 진짜 문제가 생겼습니다. 모든 UI (기본 양식 및 해당 하위 사용자 컨트롤)는 기본 기본 스레드에서 작성되었습니다. usercontrol의 LOAD 메소드에서 userControl의 텍스트 상자와 같은 일부 컨트롤 값을 기반으로 데이터를 가져옵니다.

의사 코드는 다음과 같습니다.

코드 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

그것이 준 예외는

교차 스레드 작업이 유효하지 않음 : 작성된 스레드 이외의 스레드에서 액세스 한 제어.

이것에 대해 더 많이 알기 위해 인터넷 검색을했고 다음 코드를 사용하는 것처럼 제안이 나왔습니다.

코드 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

그러나 그러나 나는 … 다시 광장으로 돌아온 것 같습니다. 응용 프로그램이 다시 응답하지 않게됩니다. 조건 1 번 라인의 실행으로 인한 것 같습니다. 로딩 작업은 부모 스레드에 의해 다시 수행되며 내가 생성 한 세 번째 스레드는 아닙니다.

나는 이것이 옳은지인지 아닌지를 알지 못한다. 스레딩을 처음 사용합니다.

이 문제를 어떻게 해결하고 라인 # 1 if 블록의 실행 효과는 무엇입니까?

상황은 이것입니다 : 컨트롤의 값에 따라 전역 변수에 데이터를로드하고 싶습니다. 자식 스레드에서 컨트롤 값을 변경하고 싶지 않습니다. 나는 자식 스레드에서 그것을하지 않을 것입니다.

따라서 해당 데이터를 데이터베이스에서 가져올 수 있도록 값에 액세스하는 것만 가능합니다.



답변

당으로 Prerak K의 업데이트 코멘트 (삭제 이후) :

질문을 제대로 제시하지 않은 것 같습니다.

상황은 이것입니다 : 컨트롤 값을 기반으로 전역 변수에 데이터를로드하고 싶습니다. 자식 스레드에서 컨트롤 값을 변경하고 싶지 않습니다. 나는 자식 스레드에서 그것을하지 않을 것입니다.

따라서 해당 데이터를 데이터베이스에서 가져올 수 있도록 값에 액세스하는 것만 가능합니다.

원하는 솔루션은 다음과 같습니다.

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

제어 스레드로 다시 전환 하기 전에 별도의 스레드에서 심각한 처리를 수행하십시오 . 예를 들면 다음과 같습니다.

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}


답변

UI의 스레딩 모델

스레딩 모델을 읽으십시오기본 개념을 이해하려면 UI 응용 프로그램에서 을 . 링크는 WPF 스레딩 모델을 설명하는 페이지로 이동합니다. 그러나 Windows Forms는 동일한 아이디어를 사용합니다.

UI 스레드

  • System.Windows.Forms.Control 에 액세스 할 수있는 스레드 (UI 스레드)는 하나뿐입니다. 및 해당 서브 클래스 멤버 .
  • UI 스레드와 다른 스레드에서 System.Windows.Forms.Control 멤버에 액세스하려고 하면 스레드 간 예외가 발생합니다.
  • 스레드가 하나뿐이므로 모든 UI 작업은 해당 스레드에 작업 항목으로 대기합니다.

여기에 이미지 설명을 입력하십시오

여기에 이미지 설명을 입력하십시오

BeginInvoke 및 Invoke 메소드

  • UI 스레드가 사용되기 때문에 호출되는 메소드의 컴퓨팅 오버 헤드와 이벤트 핸들러 메소드의 오버 헤드도 작아야합니다. 이는 사용자 입력을 처리하는 것과 동일합니다. 이것이 System.Windows.Forms.Control.Invoke 또는 System.Windows.Forms.Control.BeginInvoke 인지에 관계없이 .
  • 고가의 연산을 수행하려면 항상 별도의 스레드를 사용하십시오. .NET 2.0 BackgroundWorker 는 Windows Forms에서 고가의 연산을 수행하는 데 전념하고 있습니다. 그러나 새로운 솔루션에서는 여기에 설명 된대로 async-await 패턴을 사용해야합니다 .
  • 사용자 인터페이스를 업데이트 하려면 System.Windows.Forms.Control.Invoke 또는 System.Windows.Forms.Control.BeginInvoke 메소드 만 사용하십시오 . 무거운 계산에 사용하면 응용 프로그램이 다음을 차단합니다.

여기에 이미지 설명을 입력하십시오

불러

여기에 이미지 설명을 입력하십시오

BeginInvoke

여기에 이미지 설명을 입력하십시오

코드 솔루션

질문에 대한 답변 읽기 C #의 다른 스레드에서 GUI를 업데이트하는 방법은 무엇입니까? . C # 5.0 및 .NET 4.5의 경우 권장 솔루션은 다음과 같습니다 .


답변

당신은 사용하고자하는 Invoke또는 BeginInvoke작품의 최소한의 조각의 UI를 변경해야합니다. “무거운”방법은 다른 스레드 (예 :를 통해 BackgroundWorker) 에서 실행 한 다음 Control.Invoke/ Control.BeginInvoke를 사용하여 UI를 업데이트해야합니다. 그렇게하면 UI 스레드가 UI 이벤트 등을 자유롭게 처리 할 수 ​​있습니다.

기사가 현장에 도착 하기 전에 작성되었지만 WinForms 예제에 대한 스레딩 기사 를 참조하십시오 . 그런 점에서 업데이트하지 않았습니다. 콜백을 약간 단순화합니다.BackgroundWorkerBackgroundWorker


답변

너무 늦었 어 그러나 오늘날에도 크로스 스레드 컨트롤에 액세스하는 데 문제가 있습니까? 이것은 날짜까지 가장 짧은 답변입니다 : P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

이것은 스레드에서 양식 컨트롤에 액세스하는 방법입니다.


답변

나는이 문제를 FileSystemWatcher겪었고 다음 코드가 문제를 해결한다는 것을 알았습니다.

fsw.SynchronizingObject = this

그런 다음 컨트롤은 현재 폼 개체를 사용하여 이벤트를 처리하므로 동일한 스레드에있게됩니다.


답변

양식과 관련된 모든 메소드 내에서 너무 장황하고 불필요하게 처리 해야하는 체크 앤 호출 코드를 발견했습니다. 다음은 간단한 확장 방법으로 완전히 제거 할 수 있습니다.

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del)
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

그리고 당신은 단순히 이것을 할 수 있습니다 :

textbox1.Invoke(t => t.Text = "A");

더 이상 혼란스럽지 않습니다-간단합니다.


답변

.NET의 컨트롤은 일반적으로 스레드로부터 안전하지 않습니다. 즉, 스레드가 아닌 스레드에서 컨트롤에 액세스하면 안됩니다. 이 문제를 해결하려면 두 번째 샘플이 시도하는 컨트롤 을 호출 해야합니다 .

그러나 귀하의 경우에는 장기 실행 방법을 기본 스레드로 다시 전달하면됩니다. 물론, 그것은 당신이 정말로 원하는 것이 아닙니다. 메인 스레드에서 수행하는 모든 작업이 여기 저기에서 빠른 속성을 설정하도록이 부분을 다시 생각해야합니다.