问题描述
||
在我的多线程应用程序中,我需要对UI元素进行跨线程访问,并且我正在使用线程安全的方法来做到这一点。我在我的许多项目中反复使用了很多,并将它们保留在表单文件本身中会使文件看起来很丑。所以我想创建一个单独的类,在其中我可以放所有这些,并在需要时调用它们,但我遇到了麻烦。为了实例化更改控件的文本元素,我正在使用以下代码
delegate void SetTextCallback(string text,Control ctrl);
public void SetText(string text,Control ctrl)
{
if (ctrl.Invokerequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d,new object[] { text,ctrl });
}
else
{
if (ctrl.GetType() == typeof(Label))
{
ctrl.Text = text;
}
else
{
ctrl.Text += Environment.NewLine + text;
}
}
}
并将此函数称为
SetText(\"some text\",label1);
如果它在表单类中,则工作正常,如果我将其放入另一个类中,则该行出现错误
this.Invoke(d,ctrl });
有人可以告诉我如何正确执行此操作。
也可以有一个UI访问器方法来完成所有工作,也就是说,现在我有多种方法可以更改文本,一种可以更改启用的属性,一种可以更改背景色,另一种可以更改前景色。 。是否可以用类似的方法做到这一点
public void ChangePropert(Control ctrl,Property prop,Value val)
解决方法
所有这些的问题是您开始泄漏控件实际所在的窗体之外的UI代码。线程不必知道控件,它应该工作并更新主线程,并让主线程担心需要在UI中完成什么。
完成此操作的方法是具有第二个线程可以调用的回调,但是强制该回调实际上在主线程上执行,而不是在第二个线程上执行。您可以通过使用同步上下文来完成此操作。
您需要将辅助线程包装在一个类中,该类可以保留对主线程同步上下文的引用。然后,辅助线程可以将其用于回调。
例:
public partial class Form1 : Form
{
private SynchronizationContext _synchronizationContext;
public Form1()
{
InitializeComponent();
//Client must be careful to create sync context somehwere they are sure to be on main thread
_synchronizationContext = AsyncOperationManager.SynchronizationContext;
}
//Callback method implementation - must be of this form
public void ReceiveThreadData(object threadData)
{
// This callback now exeutes on the main thread.
// Can use directly in UI without error
this.listBoxMain.Items.Add((string)threadData);
}
private void DoSomeThreadWork()
{
// Thread needs callback and sync context so it must be wrapped in a class.
SendOrPostCallback callback = new SendOrPostCallback(ReceiveThreadData);
SomeThreadTask task = new SomeThreadTask(_synchronizationContext,callback);
Thread thread = new Thread(task.ExecuteThreadTask);
thread.Start();
}
private void button1_Click(object sender,EventArgs e)
{
DoSomeThreadWork();
}
}
您的线程类将如下所示:
/// SomeThreadTask defines the work a thread needs to do and also provides any data ///required along with callback pointers etc.
/// Populate a new SomeThreadTask instance with a synch context and callnbackl along with ///any data the thread needs
/// then start the thread to execute the task.
/// </summary>
public class SomeThreadTask
{
private string _taskId;
private SendOrPostCallback _completedCallback;
private SynchronizationContext _synchronizationContext;
/// <summary>
/// Get instance of a delegate used to notify the main thread when done.
/// </summary>
internal SendOrPostCallback CompletedCallback
{
get { return _completedCallback; }
}
/// <summary>
/// Get SynchronizationContext for main thread.
/// </summary>
internal SynchronizationContext SynchronizationContext
{
get { return _synchronizationContext; }
}
/// <summary>
/// Thread entry point function.
/// </summary>
public void ExecuteThreadTask()
{
//Just sleep instead of doing any real work
Thread.Sleep(5000);
string message = \"This is some spoof data from thread work.\";
// Execute callback on synch context to tell main thread this task is done.
SynchronizationContext.Post(CompletedCallback,(object)message);
}
public SomeThreadTask(SynchronizationContext synchronizationContext,SendOrPostCallback callback)
{
_synchronizationContext = synchronizationContext;
_completedCallback = callback;
}
}
现在,您可以摆脱每个控件上的所有调用废话。
,您可以将这些东西作为扩展方法分开。这样一来,您就可以在对象本身中调用方法,而不必像现在一样将其作为参数传递。
所以你可以这样做:label1.SetText(\"some text\");
SetText(\"some text\",label1);
另一个好处是,您可以为每种控件类型使用单独的实现,因此可以为标签添加一个,为文本框添加一个。这会使代码更简洁。
最后,关于您有关使用反射设置属性的问题。您可以使用Type.GetProperty()方法获得对该属性的引用。这将返回一个PropertyInfo对象,您可以使用它来设置属性值,如下所示:
var textProperty = label1.GetType().GetProperty(\"Text\");
textProperty.SetValue(label1,\"some text\",null);
,就在您调试项目时,对吗?
无论如何,如果您有另一个选择不创建单独的类来操纵它,您可以在每个调用其自身线程以外的线程的“ 11”上将此“ 9”属性设置为“ 10”。
CheckForIllegalCrossThreadCalls-MSDN