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


584

시나리오가 있습니다. (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 블록의 실행 효과는 무엇입니까?

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

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


이 오류의 특정 인스턴스의 경우 해결 방법은 양식에서 BackgroundWorker를 사용하여 코드의 데이터 집약적 부분을 처리하는 것으로 나타났습니다. (즉, 모든 문제 코드를 backgroundWorker1_DoWork () 메소드에 넣고 backgroundWorker1.RunWorkerAsync ()를 통해 호출) ...이 두 소스는 올바른 방향으로 나를 지적했습니다 : stackoverflow.com/questions/4806742/… youtube.com/ watch? v = MLrrbG6V1zM
Giollia

답변:


433

당으로 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;
        }
    }
}

1
C # 프로그래밍을 한 지 얼마되지 않았지만 MSDN 기사와 패치 지식을 바탕으로 한 것 같습니다.
Jeff Hubbard

1
차이점은 BeginInvoke ()는 비동기이고 Invoke ()는 동 기적으로 실행됩니다. stackoverflow.com/questions/229554/…
frzsombor

178

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의 경우 권장 솔루션은 다음과 같습니다 .



72

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

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


여기 내 상태에서. UI를 변경하지 않아도됩니다. 자식 스레드에서 현재 값에 액세스합니다. 어떤 제안의 얼굴을 구현하기
Prerak K에게

1
속성에 액세스하기 만해도 여전히 UI 스레드로 마샬링해야합니다. 값에 액세스 할 때까지 메소드를 계속 사용할 수없는 경우 값을 리턴하는 대리자를 사용할 수 있습니다. 그러나 예, UI 스레드를 통해 가십시오.
Jon Skeet

안녕 존, 당신이 날 올바른 방향으로 향하고 있다고 믿습니다. 예, 더 이상 진행할 수없는 값이 필요합니다. 그 '값을 반환하는 델리게이트 사용하기'를 뛰어 넘을 수 있습니까? 감사합니다
Prerak K

1
Func <string>과 같은 대리자를 사용하십시오. string text = textbox1.Invoke ((Func <string>) () => textbox1.Text); (이것은 C # 3.0을 사용한다고 가정하고 있습니다. 그렇지 않으면 익명의 방법을 사용할 수 있습니다.)
Jon Skeet

45

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

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

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


1
이것은 나에게 준다 Invoke or BeginInvoke cannot be called on a control until the window handle has been created. 나는 여기에서
rupweb

42

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

fsw.SynchronizingObject = this

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


2
이것은 내 베이컨을 구했습니다. VB.NET에서는 다음을 사용했습니다..SynchronizingObject = Me
코딩 코딩

20

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

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");

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


"t"는 무엇입니까
Rawat

t이 경우 @Rawat 은 textbox1-인수로 전달됩니다
Rob

17

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

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



10

Async / Await 및 콜백을 사용하는 새로운 모습. 확장 메소드를 프로젝트에 유지하는 경우 한 줄의 코드 만 필요합니다.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Try / Catch 문으로 래핑하는 것과 같이 Extension 메서드에 다른 것을 추가하여 호출자에게 완료 후 반환 할 형식, 호출자에게 예외 콜백을 알려줄 수 있습니다.

캐치 시도, 자동 예외 로깅 및 콜백 추가

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

10

이 오류를 해결하기 위해 권장되는 방법은 아니지만 빠르게 억제 할 수 있습니다. 그러면 작업이 수행됩니다. 나는 이것을 프로토 타입이나 데모에 선호한다. 더하다

CheckForIllegalCrossThreadCalls = false

Form1()생성자입니다.


9

다른 스레드에서 객체를 수정하는 가장 간단한 방법은 다음과 같습니다.

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

간단! 감사 .
알리 Esmaeili


7

xamarin stuidio 외부의 Visual Studio Winforms 프로토 타입 프로젝트에서 iOS-Phone 모노 터치 앱 컨트롤러를 프로그래밍 할 때이 기능이 필요하다는 것을 알았습니다. 가능한 한 xamarin 스튜디오보다 VS에서 프로그래밍하는 것을 선호하기 위해 컨트롤러가 전화 프레임 워크에서 완전히 분리되기를 원했습니다. 이렇게하면 Android 및 Windows Phone과 같은 다른 프레임 워크에이를 구현하는 것이 나중에 사용하기가 훨씬 쉬워집니다.

버튼을 클릭 할 때마다 크로스 스레딩 스위칭 코드를 처리해야하는 부담없이 GUI가 이벤트에 응답 할 수있는 솔루션을 원했습니다. 기본적으로 클래스 컨트롤러가 클라이언트 코드를 단순하게 유지하도록 처리합니다. 클래스의 한 곳에서 처리 할 수있는 것처럼 더 깔끔한 GUI에 많은 이벤트가있을 수 있습니다. 저는 멀티 테스팅 전문가가 아닙니다. 결함이 있는지 알려주세요.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

GUI 양식은 컨트롤러가 비동기 작업을 실행하고 있다는 것을 인식하지 못합니다.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

6

작업중인 객체에없는 경우 다른 방법이 있습니다.

(InvokeRequired)

이것은 메인 폼에 있지만 InvokeRequired가없는 객체를 가진 메인 폼 이외의 클래스에서 메인 폼으로 작업 할 때 유용합니다.

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

위와 동일하게 작동하지만 invokerequired가있는 객체는 없지만 MainForm에 액세스 할 수있는 경우 다른 방법입니다.


5

이전 답변과 같은 줄을 따라 교차 스레드 호출 예외없이 모든 Control 속성을 사용할 수있는 매우 짧은 추가.

도우미 방법

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

샘플 사용법

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}


5

예를 들어 UI 스레드의 컨트롤에서 텍스트를 가져 오려면

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

3

같은 질문 : 다른 스레드에서 c를 업데이트하는 방법

두 가지 방법:

  1. e.result에 값을 반환하고 backgroundWorker_RunWorkerCompleted 이벤트에서 텍스트 상자 값을 설정하는 데 사용하십시오.

  2. 이러한 종류의 값을 별도의 클래스 (데이터 홀더로 작동)에서 보유 할 변수를 선언하십시오. 이 클래스의 정적 인스턴스를 생성하고 모든 스레드를 통해 액세스 할 수 있습니다.

예:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

2

간단히 이것을 사용하십시오 :

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });


0

이 문제를 해결하는 간단하고 재사용 가능한 방법입니다.

확장 방법

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

샘플 사용법

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

-3

크로스 스레드 작업에는 두 가지 옵션이 있습니다.

Control.InvokeRequired Property 

두 번째는 사용하는 것입니다

SynchronizationContext Post Method

Control.InvokeRequired는 SynchronizationContext를 어디에서나 사용할 수있는 반면 Control 클래스에서 상속 된 컨트롤을 작업 할 때만 유용합니다. 유용한 정보는 다음과 같습니다.

교차 스레드 업데이트 UI | .그물

SynchronizationContext를 사용한 크로스 스레드 업데이트 UI | .그물

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.