다른 스레드가 소유하고 있기 때문에 호출 스레드가이 오브젝트에 액세스 할 수 없습니다.


342

내 코드는 다음과 같습니다

public CountryStandards()
{
    InitializeComponent();
    try
    {
        FillPageControls();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
    popUpProgressBar.IsOpen = true;
    lblProgress.Content = "Loading. Please wait...";
    progress.IsIndeterminate = true;
    worker = new BackgroundWorker();
    worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync();                    
}

private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    GetGridData(null, 0); // filling grid
}

private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
    progress.Value = e.ProgressPercentage;
}

private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    worker = null;
    popUpProgressBar.IsOpen = false;
    //filling Region dropdown
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_REGION";
    DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
        StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");

    //filling Currency dropdown
    objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_CURRENCY";
    DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
        StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");

    if (Users.UserRole != "Admin")
        btnSave.IsEnabled = false;

}

/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging)   </pamam>
private void GetGridData(object sender, int pageIndex)
{
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT";
    objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
    DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
    {
        DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
        dgCountryList.ItemsSource = objDataTable.DefaultView;
    }
    else
    {
        MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
        btnClear_Click(null, null);
    }
}

objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;그리드 데이터 가져 오기 단계 에서 예외가 발생 함

다른 스레드가 소유하고 있으므로 호출 스레드가이 오브젝트에 액세스 할 수 없습니다.

무슨 일이야?


답변:


699

이것은 사람들이 시작하는 일반적인 문제입니다. 메인 스레드 이외의 스레드에서 UI 요소를 업데이트 할 때마다 다음을 사용해야합니다.

this.Dispatcher.Invoke(() =>
{
    ...// your code here.
});

control.Dispatcher.CheckAccess()현재 스레드가 컨트롤을 소유하는지 여부를 확인할 수도 있습니다 . 그것이 소유하고 있다면 코드는 정상적으로 보입니다. 그렇지 않으면 위 패턴을 사용하십시오.


3
OP와 같은 문제가 있습니다. 내 문제는 이제 이벤트로 인해 스택 오버플로가 발생한다는 것입니다. : \
Malavos

2
이전 프로젝트로 돌아가서 해결했습니다. 또한, 나는 이것을 +1하는 것을 잊었다. 이 방법은 아주 잘 작동합니다! 스레드를 사용하여 지역화 된 리소스를로드하는 것만으로도 응용 프로그램로드 시간이 10 초 이상으로 향상됩니다. 건배!
Malavos

4
내가 틀리지 않으면 비 소유자 스레드에서 UI 객체를 읽을 수도 없습니다. 조금 놀랐습니다.
Elliot

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);이 답변
JumpingJezza

2
+1. 하아! 나는 WPF 해커를 위해 이것을 사용하여 사물을 분리했습니다. 나는 정적 컨텍스트에 있었기 때문에 this.Dispatcher.Invoke.... 대신 사용할 수 없었습니다 ... myControl.Dispatcher.Invoke:) 객체를 다시 반환해야했습니다 myControlDispatcher.Invoke<object>(() => myControl.DataContext).
C. Tewalt

53

다른 용도로 Dispatcher.Invoke는 다른 작업을 수행하는 함수에서 UI를 즉시 업데이트하는 것입니다.

// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);

버튼 텍스트를 " Processing ... " 으로 업데이트 하고 WebClient요청 하는 동안 비활성화 합니다.


4
이 답변은 메타에 대해 논의 중입니다. meta.stackoverflow.com/questions/361844/…
JDB는 여전히 Monica

인터넷에서 데이터를 가져 오는 것을 제어하지 못하게 되었습니까?
Waseem Ahmad Naeem

41

내 2 센트를 추가하기 위해를 통해 코드를 호출하더라도 예외가 발생할 수 있습니다 System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
요점은 당신이 전화를해야한다는 것입니다 Invoke()Dispatcher액세스하려는 것을 제어 일부의 경우와 동일하지 않을 수있는 System.Windows.Threading.Dispatcher.CurrentDispatcher. 대신 YourControl.Dispatcher.Invoke()안전을 위해 사용해야합니다. 나는 이것을 깨닫기 전에 두 시간 동안 머리를 두드리고 있었다.

최신 정보

향후 독자를 위해 최신 버전의 .NET (4.0 이상)에서는 이것이 변경된 것으로 보입니다. 이제 VM에서 UI 백업 속성을 업데이트 할 때 올바른 디스패처에 대해 더 이상 걱정할 필요가 없습니다. WPF 엔진은 올바른 UI 스레드에서 스레드 간 호출을 마샬링합니다. 자세한 내용은 여기를 참조 하십시오 . 정보 및 링크에 대한 @aaronburro에게 감사합니다. 아래의 의견을 주석으로 읽으십시오.


4
@ l33t : WPF는 하나의 응용 프로그램에서 여러 개의 UI 스레드를 지원하며 각 응용 프로그램에는 자체 UI가 있습니다 Dispatcher. 이러한 경우 (드물게 드물지만) 호출 Control.Dispatcher은 안전한 접근 방법입니다. 참고로 당신이 볼 수있는 이 문서 뿐만 아니라 이 SO 포스트 (특히 스퀴드 워드의 답변을).
dotNET

1
흥미롭게도, 나는이 페이지에 googled하고 착륙했을 때이 예외에 직면하고 있었고 우리 대부분이 그랬듯이 가장 높은 투표 답변을 시도했는데 그때 문제가 해결되지 않았습니다. 그런 다음이 이유를 찾아 동료 개발자를 위해 여기에 게시했습니다.
dotNET

1
@ l33t, MVVM을 올바르게 사용하면 문제가되지 않습니다. ViewModels와 Models는 컨트롤에 대해 아무것도 모르고 컨트롤을 알 필요가 없지만 뷰는 반드시 사용중인 디스패처를 알고 있습니다.
aaronburro

1
@aaronburro : 문제는 VM이 ​​대체 스레드 (예 : 작업, 타이머 기반 작업, 병렬 쿼리)에서 작업을 시작하려고 할 수 있으며 작업이 진행됨에 따라 UI (RaisePropertyChanged 등을 통해)를 업데이트하려고 할 수 있습니다. 비 UI 스레드에서 UI 컨트롤에 액세스하여이 예외가 발생합니다. 이 문제를 해결할 올바른 MVVM 접근 방법 을 모르겠습니다 .
dotNET

1
WPF 바인딩 엔진은 특성 변경 이벤트를 올바른 Dispatcher에 자동으로 마샬링합니다. 이것이 VM이 디스패처를 알 필요가없는 이유입니다. 단지 속성 변경 이벤트를 발생시키기 만하면됩니다. WinForms 바인딩은 다른 이야기입니다.
aaronburro

34

이 문제가 발생하고 UI 컨트롤은에 작성된 경우 별도의 작업자 와 작업 할 때 스레드 BitmapSource또는 ImageSource전화, WPF에 Freeze()먼저 전달하기 전에 방법 BitmapSource또는 ImageSource어떤 메서드에 매개 변수로. Application.Current.Dispatcher.Invoke()그러한 경우에는 사용 이 작동하지 않습니다


24
아, 아무도 이해하지 못하는 것을 해결하는 좋은 오래된 모호하고 신비한 트릭 같은 것은 없습니다.
Edwin

2
왜 이것이 작동하는지, 어떻게 알아낼 수 있었는지에 대한 자세한 정보를 원합니다.
Xavier Shay


25

내가 access UI구성 요소를 시도했기 때문에 이것은 나와 함께 일어났다another thread insted of UI thread

이처럼

private void button_Click(object sender, RoutedEventArgs e)
{
    new Thread(SyncProcces).Start();
}

private void SyncProcces()
{
    string val1 = null, val2 = null;
    //here is the problem 
    val1 = textBox1.Text;//access UI in another thread
    val2 = textBox2.Text;//access UI in another thread
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2);
}

이 문제를 해결하려면 Candide가 그의 답변에서 언급 한 내용으로 UI 호출을 포장

private void SyncProcces()
{
    string val1 = null, val2 = null;
    this.Dispatcher.Invoke((Action)(() =>
    {//this refer to form in WPF application 
        val1 = textBox.Text;
        val2 = textBox_Copy.Text;
    }));
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2 );
}

1
Upvoted,이 때문에 하지 중복 응답 또는 plagiaristic하지만, 대신 이전에 게시 한 것에 대해 신용을 제공하면서 다른 답변이 결여되었다는 것을 좋은 예를 제공합니다.
Panzercrisis

공감대는 분명한 대답입니다. 다른 사람들도 똑같이 썼지 만 이것은 갇힌 사람에게는 분명합니다.
NishantM

15

어떤 이유로 Candide의 답변이 구축되지 않았습니다. 그래도 이것이 도움이되어 완벽하게 작동했습니다.

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));

양식의 클래스에서 전화하지 않았을 수 있습니다. Window에 대한 참조를 얻거나 제안한 것을 사용할 수 있습니다.
Simone

4
그것이 당신을 위해 일했다면, 그것을 처음부터 사용할 필요가 없었습니다. System.Windows.Threading.Dispatcher.CurrentDispatcher이다 현재 스레드에 대한 디스패처 . 즉, 백그라운드 스레드를 사용하는 경우 UI 스레드의 디스패처 가 되지 않습니다 . UI 스레드 디스패처에 액세스하려면을 사용하십시오 System.Windows.Application.Current.Dispatcher.

13

UI를 업데이트해야합니다.

Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)})); 

4

이것은 나를 위해 작동합니다.

new Thread(() =>
        {

        Thread.CurrentThread.IsBackground = false;
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {

          //Your Code here.

        }, null);
        }).Start();

3

또한 System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()dotNet이 답변에 쓴 것처럼 항상 대상 제어의 디스패처가 아니라는 것을 알았 습니다. 컨트롤 자체 디스패처에 액세스 할 수 없었으므로 Application.Current.Dispatcher문제를 해결했습니다.


2

문제는 GetGridData백그라운드 스레드에서 호출한다는 것 입니다. 이 방법은 메인 스레드에 바인딩 된 여러 WPF 컨트롤에 액세스합니다. 백그라운드 스레드에서 액세스하려고하면이 오류가 발생합니다.

올바른 실로 돌아가려면을 사용해야합니다 SynchronizationContext.Current.Post. 그러나이 특별한 경우에는 UI 기반 작업이 대부분입니다. 따라서 즉시 UI 스레드로 돌아가서 일부 작업을 수행하기 위해 백그라운드 스레드를 작성합니다. 백그라운드 스레드에서 값 비싼 작업을 수행하고 나중에 새 데이터를 UI 스레드에 게시 할 수 있도록 코드를 약간 리팩터링해야합니다.


2

여기 에서 언급했듯이 Dispatcher.InvokeUI를 정지시킬 수 있습니다. Dispatcher.BeginInvoke대신 사용해야 합니다.

다음은 점검 및 호출 디스패처 호출을 단순화하는 편리한 확장 클래스입니다.

샘플 사용법 : (WPF 창에서 호출)

this Dispatcher.InvokeIfRequired(new Action(() =>
{
    logTextbox.AppendText(message);
    logTextbox.ScrollToEnd();
}));

확장 클래스 :

using System;
using System.Windows.Threading;

namespace WpfUtility
{
    public static class DispatcherExtension
    {
        public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
        {
            if (dispatcher == null)
            {
                return;
            }
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
                return;
            }
            action();
        }
    }
}

0

또한 다른 솔루션은 예를 들어 백그라운드 작업자 스레드가 아닌 UI 스레드에서 컨트롤을 만드는 것입니다.


0

계단식 콤보 상자를 WPF 응용 프로그램에 추가 할 때 오류가 계속 발생 하고이 API를 사용하여 오류를 해결했습니다.

    using System.Windows.Data;

    private readonly object _lock = new object();
    private CustomObservableCollection<string> _myUiBoundProperty;
    public CustomObservableCollection<string> MyUiBoundProperty
    {
        get { return _myUiBoundProperty; }
        set
        {
            if (value == _myUiBoundProperty) return;
            _myUiBoundProperty = value;
            NotifyPropertyChanged(nameof(MyUiBoundProperty));
        }
    }

    public MyViewModelCtor(INavigationService navigationService) 
    {
       // Other code...
       BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );

    }

자세한 내용은 참조하십시오 https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version을 하십시오. % 3Dv4.7); k (DevLang-csharp) & rd = true

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