크로스스레드 작업이 유효하지 않습니다:생성된 스레드가 아닌 다른 스레드에서 액세스되는 제어

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

문제

시나리오가 있습니다.(윈도우 폼, 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번줄 if 조건이 실행되었기 때문인 것 같습니다.로딩 작업은 내가 생성한 세 번째 스레드가 아닌 상위 스레드에 의해 다시 수행됩니다.

내가 이것을 옳았는지 그른지 인식했는지 모르겠습니다.저는 스레딩을 처음 사용합니다.

이 문제를 어떻게 해결하고 Line#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 양식은 동일한 아이디어를 사용합니다.

UI 스레드

  • 액세스 할 수있는 스레드 (UI 스레드)가 하나뿐입니다. System.windows.forms.control 그리고 서브 클래스 회원.
  • 회원에 액세스하려고 시도합니다 System.windows.forms.control UI 스레드와는 다른 스레드에서 스레드는 교차 스레드 예외를 유발합니다.
  • 스레드가 하나만 있으므로 모든 UI 작업은 해당 스레드의 작업 항목으로 대기됩니다.

enter image description here

enter image description here

시작하고 메소드를 호출하십시오

  • UI 스레드가 사용되기 때문에 호출되는 방법의 컴퓨팅 오버 헤드는 작고 이벤트 핸들러 메소드의 컴퓨팅 오버 헤드를 계산해야합니다. 사용자 입력 처리를 담당하는 것과 동일합니다. 이것이 여부에 관계없이 System.Windows.forms.control.invoke 또는 System.windows.forms.control.begininvoke.
  • 고가의 작동을 수행하려면 항상 별도의 스레드를 사용하십시오. .NET 2.0 이후 배경 노동자 Windows 양식에서 비싼 작업을 수행하는 데 전념하고 있습니다. 그러나 새로운 솔루션에서는 설명대로 비동기 인식 패턴을 사용해야합니다. 여기.
  • 사용 System.Windows.forms.control.invoke 또는 System.windows.forms.control.begininvoke 메소드 사용자 인터페이스를 업데이트하는 방법. 무거운 계산에 사용하면 응용 프로그램이 차단됩니다.

enter image description here

부르다

enter image description here

BegininVoke

enter image description here

코드 솔루션

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

UI를 변경하는 데 필요한 최소한의 작업에만 Invoke 또는 BeginInvoke를 사용하려고 합니다."무거운" 메소드는 다른 스레드에서 실행되어야 합니다(예:BackgroundWorker를 통해) UI를 업데이트하기 위해 Control.Invoke/Control.BeginInvoke를 사용합니다.이렇게 하면 UI 스레드가 UI 이벤트 등을 자유롭게 처리할 수 있습니다.

내 것을 보아라 스레딩 기사 한 동안 WinForms 예 - 비록 이 기사는 BackgroundWorker가 현장에 도착하기 전에 작성되었지만, 유감스럽게도 그 점에 대해서는 업데이트하지 않았습니다.BackgroundWorker는 콜백을 약간 단순화할 뿐입니다.

나는이 문제가 있었다 FileSystemWatcher 다음 코드가 문제를 해결했다는 것을 발견했습니다.

fsw.SynchronizingObject = this

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

나는 지금 너무 늦었다는 것을 알고 있습니다. 그러나 오늘날에도 크로스 스레드 컨트롤에 액세스하는 데 어려움이 있습니까? 이것은 지금까지 가장 짧은 대답입니다 : p

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

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

.NET의 컨트롤은 일반적으로 스레드 안전하지 않습니다. 즉, 사는 곳 이외의 스레드에서 컨트롤에 액세스해서는 안됩니다. 이 문제를 해결하려면 필요합니다 부르다 두 번째 샘플이 시도하는 컨트롤입니다.

그러나 귀하의 경우에 한 모든 일은 장기 실행 방법을 기본 스레드로 전달하는 것입니다. 물론, 그것은 실제로 당신이하고 싶은 일이 아닙니다. 메인 스레드에서 수행하는 모든 일이 여기저기서 빠른 속성을 설정할 수 있도록 이것을 조금 다시 생각해야합니다.

나는 너무 장황하고 불필요한 양식과 관련된 모든 방법 내에서 흩어져 있어야하는 체크 앤 인 침수 코드를 발견했습니다. 다음은 간단한 확장 방법이 있습니다.

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

더 이상 엉망이되지 않습니다 - 단순합니다.

UI 크로스 스레딩 문제에 대한 가장 깨끗하고 적절한 솔루션은 SynchronizationContext를 사용하는 것입니다. 다중 스레드 애플리케이션에서 UI에 호출을 동기화합니다 기사, 그것은 그것을 아주 멋지게 설명합니다.

Async/Await 및 Callbacks를 사용하는 새로운 모양. 프로젝트에서 확장 방법을 유지하는 경우 코드 한 줄만 필요합니다.

/// <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 문으로 래핑하는 것과 같은 확장 방법에 다른 것들을 추가 할 수 있습니다. 발신자는 완료 후 반환 할 유형, 발신자에 대한 예외 콜백을 알려줄 수 있습니다.

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

    /// <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
        }
    }

배경 작업자 예를 살펴 봐야합니다.
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx특히 UI 계층과 상호 작용하는 방법. 게시를 바탕으로 문제에 답하는 것 같습니다.

가장 간단한 (내 의견으로는) 다른 스레드에서 객체를 수정하는 가장 간단한 (내 의견으로는)

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;
        }
    }
}

Xamarin Stuidio 외부의 Visual Studio Winforms 프로토 타입 프로젝트에서 iOS-Phone Monotouch 앱 컨트롤러를 프로그래밍하는 동안이 문제가 필요하다는 것을 알았습니다. Xamarin Studio를 통해 가능한 한 프로그램을 선호하면서 컨트롤러가 전화 프레임 워크에서 완전히 분리되기를 원했습니다. 이러한 방식으로 Android 및 Windows Phone과 같은 다른 프레임 워크를 위해이를 구현하는 것은 향후 용도로 훨씬 쉬울 것입니다.

GUI가 모든 버튼 클릭 뒤에 크로스 스레딩 전환 코드를 다루는 부담없이 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);
            }
        }
    }
}

이것은이 오류를 해결하는 것이 권장되는 방법은 아니지만 빨리 억제 할 수 있습니다. 작업을 수행합니다. 프로토 타입이나 데모에 대해 이것을 선호합니다. 추가하다

CheckForIllegalCrossThreadCalls = false

안에 Form1() 생성자.

작업중인 객체에

(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;
}

위와 동일하게 작동하지만 광고가 발생한 객체가 없지만 메인 폼에 액세스 할 수있는 경우 다른 접근 방식입니다.

이전 답변과 동일한 줄을 따라 크로스 스레드 호출 예외없이 모든 제어 속성을 사용할 수있는 매우 짧은 추가.

도우미 방법

/// <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;
}
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

예를 들어 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

동일한 질문 : up-up-update-the-gui-from-onother-thread-in-c

두 가지 방법:

  1. E.Result에서 값을 반환하고이를 사용하여 BackgroundWorker_RUNWORKERCOMPLETED 이벤트에서 Yout TextBox 값을 설정합니다.

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

예시:

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

행동 Y; // 클래스 내에서 선언했습니다

label1.invoke (y = () => label.text = "text");

간단히 사용하십시오 :

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

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

Control.InvokeRequired Property 

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

SynchronizationContext Post Method

Control.invokerequired는 제어 클래스에서 상속 된 작업 제어가 어디에서나 사용할 수있는 경우에만 유용합니다. 유용한 정보는 다음 링크와 마찬가지로입니다

크로스 스레드 업데이트 UI | .그물

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

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