Mój kod jest jak poniżej
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);
}
}
Krok objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
w uzyskaniu danych siatki generuje wyjątek
Wątek wywołujący nie może uzyskać dostępu do tego obiektu, ponieważ inny wątek jest jego właścicielem.
Co tu jest nie tak?
c#
wpf
multithreading
backgroundworker
Kuntady Nithesh
źródło
źródło
Odpowiedzi:
Jest to powszechny problem z rozpoczynaniem pracy przez ludzi. Ilekroć aktualizujesz elementy interfejsu użytkownika z wątku innego niż główny wątek, musisz użyć:
Możesz także użyć
control.Dispatcher.CheckAccess()
do sprawdzenia, czy bieżący wątek jest właścicielem kontrolki. Jeśli jest właścicielem, kod wygląda normalnie. W przeciwnym razie użyj powyższego wzoru.źródło
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);
dostać dyspozytora, jeśli nie w wątku interfejsu użytkownika, zgodnie z tą odpowiedziąthis.Dispatcher.Invoke
.... zamiast tego ...myControl.Dispatcher.Invoke
:) Musiałem zwrócić obiekt, więc zrobiłem tomyControlDispatcher.Invoke<object>(() => myControl.DataContext)
;Innym dobrym zastosowaniem
Dispatcher.Invoke
jest natychmiastowa aktualizacja interfejsu użytkownika w funkcji, która wykonuje inne zadania:Używam tego, aby zaktualizować tekst przycisku do „ Przetwarzanie ... ” i wyłączyć go podczas
WebClient
wysyłania żądań.źródło
Aby dodać moje 2 centy, wyjątek może wystąpić nawet po wywołaniu kodu
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
.Chodzi o to, że trzeba zadzwonić
Invoke()
zDispatcher
tego sterowania, który próbujesz dostępu , które w niektórych przypadkach mogą nie być takie same jakSystem.Windows.Threading.Dispatcher.CurrentDispatcher
. Zamiast tego powinieneś użyćYourControl.Dispatcher.Invoke()
aby być bezpiecznym. Waliłem głową przez kilka godzin, zanim zdałem sobie z tego sprawę.Aktualizacja
Dla przyszłych czytelników wygląda na to, że zmieniło się to w nowszych wersjach .NET (4.0 i nowszych). Teraz nie musisz się już martwić o poprawność programu rozsyłającego podczas aktualizowania właściwości tworzenia kopii zapasowych interfejsu użytkownika na maszynie wirtualnej. Silnik WPF będzie kierował wywołania wątków we właściwym wątku interfejsu użytkownika. Zobacz więcej szczegółów tutaj . Dzięki @aaronburro za informacje i link. Możesz również przeczytać naszą rozmowę poniżej w komentarzach.
źródło
Dispatcher
. W tych przypadkach (które są co prawda rzadkie) dzwonienieControl.Dispatcher
jest bezpiecznym podejściem. W celach informacyjnych możesz zobaczyć ten artykuł, a także ten post SO (szczególnie odpowiedź Skalmar).Jeśli napotkasz ten problem i formanty interfejsu użytkownika zostały utworzone w osobnym wątku roboczym podczas pracy z WPF
BitmapSource
lubImageSource
w nim, wywołajFreeze()
metodę najpierw przed przekazaniem parametruBitmapSource
lubImageSource
jako parametru do dowolnej metody. UżywanieApplication.Current.Dispatcher.Invoke()
nie działa w takich przypadkachźródło
stało się to ze mną, ponieważ próbowałem się
access UI
włączyćanother thread insted of UI thread
lubię to
aby rozwiązać ten problem, zawiń każde wywołanie interfejsu użytkownika wewnątrz tego, co Candide wspomniał powyżej w swojej odpowiedzi
źródło
Z jakiegoś powodu odpowiedź Kandyda nie została zbudowana. Było to jednak pomocne, ponieważ doprowadziło mnie do znalezienia tego, co działało idealnie:
źródło
System.Windows.Threading.Dispatcher.CurrentDispatcher
jest dyspozytorem bieżącego wątku . Oznacza to, że jeśli jesteś w wątku w tle, nie będzie to dyspozytor wątku interfejsu użytkownika. Aby uzyskać dostęp do programu rozsyłającego wątek interfejsu użytkownika, użyjSystem.Windows.Application.Current.Dispatcher
.Musisz zaktualizować interfejs użytkownika, więc użyj
źródło
To działa dla mnie.
źródło
Odkryłem również, że
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
nie zawsze jest to dyspozytor kontroli celu, tak jak napisał dotNet w swojej odpowiedzi. Nie miałem dostępu do własnego dyspozytora kontroli, więc skorzystałemApplication.Current.Dispatcher
i to rozwiązało problem.źródło
Problem polega na tym, że dzwonisz
GetGridData
z wątku w tle. Ta metoda umożliwia dostęp do kilku elementów sterujących WPF, które są powiązane z głównym wątkiem. Każda próba dostępu do nich z wątku w tle prowadzi do tego błędu.Aby wrócić do właściwego wątku, powinieneś użyć
SynchronizationContext.Current.Post
. Jednak w tym konkretnym przypadku wydaje się, że większość pracy, którą wykonujesz, oparta jest na interfejsie użytkownika. W związku z tym należy utworzyć wątek w tle, aby natychmiast wrócić do wątku interfejsu użytkownika i wykonać trochę pracy. Musisz nieco zmienić kod, aby mógł wykonać kosztowną pracę w wątku w tle, a następnie opublikować nowe dane w wątku interfejsu użytkownikaźródło
Jak wspomniano tutaj ,
Dispatcher.Invoke
może zamrozić interfejs użytkownika. Powinien użyćDispatcher.BeginInvoke
zamiast.Oto przydatna klasa rozszerzeń, która upraszcza sprawdzanie i wywoływanie wywołań programu rozsyłającego.
Przykładowe użycie: (połączenie z okna WPF)
Klasa rozszerzenia:
źródło
Innym rozwiązaniem jest zapewnienie, że formanty są tworzone w wątku interfejsu użytkownika, a nie na przykład w wątku procesu roboczego w tle.
źródło
Wciąż pojawiał się błąd, gdy dodawałem kaskadowe kombinacje do mojej aplikacji WPF i rozwiązałem ten błąd, używając tego interfejsu API:
Aby uzyskać szczegółowe informacje, zobacz 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
źródło