我的代码如下
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;
会引发异常
调用线程无法访问此对象,因为它不同 线程拥有它。
这里有什么问题?
答案 0 :(得分:592)
这是人们入门的常见问题。每当您从主线程以外的线程更新UI元素时,您需要使用:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
您还可以使用control.Dispatcher.CheckAccess()
检查当前线程是否拥有该控件。如果它拥有它,您的代码看起来很正常。否则,请使用上述模式。
答案 1 :(得分:45)
Dispatcher.Invoke
的另一个好用途是立即更新执行其他任务的函数中的UI:
// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
我用它将按钮文本更新为&#34; 处理... &#34;并在发出WebClient
次请求时将其停用。
答案 2 :(得分:36)
要添加2美分,即使您通过System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
调用代码,也会发生异常。
关键是您必须致电{strong}控件的Invoke()
Dispatcher
,而您有权访问,在某些情况下可能不是与System.Windows.Threading.Dispatcher.CurrentDispatcher
相同。因此,您应该使用YourControl.Dispatcher.Invoke()
来保证安全。在我意识到这一点之前,我正在敲打我的头几个小时。
对于未来的读者,在较新版本的.NET(4.0及更高版本)中看起来已经发生了变化。现在,在更新VM中的UI支持属性时,您不再需要担心正确的调度程序。 WPF引擎将在正确的UI线程上封送跨线程调用。查看更多详情here。感谢@aaronburro的信息和链接。您可能还想在评论中阅读下面的对话。
答案 3 :(得分:28)
如果有人试图在WPF和线程中使用BitmapSource
并且有相同的消息:在传递Freeze()
作为线程参数之前,先调用BitmapSource
方法。
答案 4 :(得分:21)
这件事发生在我身上,因为我尝试了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);
}
要解决此问题,请在what Candide mentioned above in his answer
中包装任何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 );
}
答案 5 :(得分:13)
您需要更新到用户界面,因此请使用
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
答案 6 :(得分:13)
出于某种原因,Candide的回答并没有建立起来。不过,这很有帮助,因为它让我找到了这个,它完美地运作了:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
答案 7 :(得分:3)
我还发现System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
并不总是目标控制的调度员,就像dotNet在他的回答中所写的那样。我没有访问控制自己的调度程序,因此我使用Application.Current.Dispatcher
并解决了问题。
答案 8 :(得分:2)
问题是您是从后台线程调用GetGridData
。此方法访问绑定到主线程的几个WPF控件。任何从后台线程访问它们的尝试都将导致此错误。
为了回到正确的线程,你应该使用SynchronizationContext.Current.Post
。但是在这种特殊情况下,您所做的大部分工作似乎都是基于UI的。因此,您将创建一个后台线程,以便立即返回到UI线程并完成一些工作。您需要稍微重构一下代码,以便它可以在后台线程上执行昂贵的工作,然后将新数据发布到UI线程
答案 9 :(得分:1)
如上所述here,Dispatcher.Invoke
可以冻结用户界面。应改为使用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();
}
}
}
答案 10 :(得分:1)
这适合我。
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
答案 11 :(得分:0)
另外,另一个解决方案是确保您的控件是在UI线程中创建的,而不是由后台工作线程创建的。
答案 12 :(得分: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 );
}
答案 13 :(得分:0)
根据您的需求,肯定有不同的方法。
我使用UI更新线程(不是主UI线程)的一种方法是让该线程启动一个循环,在该循环中将整个逻辑处理循环调用到UI线程上。
示例:
public SomeFunction()
{
bool working = true;
Thread t = new Thread(() =>
{
// Don't put the working bool in here, otherwise it will
// belong to the new thread and not the main UI thread.
while (working)
{
Application.Current.Dispatcher.Invoke(() =>
{
// Put your entire logic code in here.
// All of this code will process on the main UI thread because
// of the Invoke.
// By doing it this way, you don't have to worry about Invoking individual
// elements as they are needed.
});
}
});
}
这样,代码将完全在主UI线程上执行。对于那些难以集中精力处理跨线程操作的业余程序员来说,这可能是一个专家。但是,它很容易成为更复杂的UI的缺点(尤其是在执行动画时)。确实,这只是在伪造一个系统,以更新UI,然后返回以处理已触发的任何事件,以代替有效的跨线程操作。
答案 14 :(得分:0)
有时可能是您创建的对象引发了异常,而不是我显然正在查看的目标。
在我这里的代码中:
xaml 文件:
<Grid Margin="0,0,0,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<TextBlock x:Name="tbScreenLog" VerticalAlignment="Stretch" Background="Black" FontSize="12" Foreground="#FF919191" HorizontalAlignment="Stretch"/>
</Grid>
xaml.cs 文件:
System.Windows.Documents.Run rnLine = new System.Windows.Documents.Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
Dispatcher.Invoke(()=> {
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;
我收到了从不同线程访问对象的异常,但我是在 UI 线程上调用它??
过了一段时间后,我觉得这不是关于 TextBlock 对象,而是关于我在调用之前创建的 Run 对象。
将代码更改为此解决了我的问题:
Dispatcher.Invoke(()=> {
Run rnLine = new Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
tbScreenLog.Inlines.Add(rnLine);
});
LineAlternate = !LineAlternate;