2011-05-23 18 views
3

在我的多线程应用程序中,我需要跨UI线程访问UI元素,我使用线程安全方法来做到这一点。我在我的许多项目中反复使用了这一点,并将它们保存在表单文件中,这使得文件看起来很难看。所以我想创建一个分离类,在那里我可以把所有这些,并在需要时打电话给他们,但我有麻烦。对于instace改变控件的文本元素,我使用以下保持线程安全的UI访问器在一个单独的类在c#

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, new object[] { text, ctrl }); 

有人可以告诉我,我该如何做到这一点。

也有可能有一个UI访问器方法做所有的东西,那就是现在我有像这样的多种方法来改变文本之一来改变启用的属性之一来改变背景颜色和一个改变前面的颜色。是否有可能有类似

public void ChangePropert(Control ctrl,Property prop,Value val) 
+0

您能否确认您在调用时遇到的异常?我想它的通用线程异常解释控制只能由它的所有者访问。此外,请参阅有关属性的反思,其中的问题将是类型安全。 – Smudge202 2011-05-23 08:34:38

回答

2

这一切的问题是你开始泄漏,其中控制实际居住形式之外的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; 
    } 

} 

现在你可以摆脱对每个控件的调用所有废话。

-1

做这仅仅是当你调试你的项目,对不对?无论如何,如果你有另一个选项不创建一个单独的类来操纵这个,你可以设置这个CheckForIllegalCrossThreadCalls属性为false每个form调用线程以外的其自己的线程。

CheckForIllegalCrossThreadCalls - MSDN

+0

我很好奇。你能否扩展这个答案,请给出一个用法的概念? (Pretty please)=) – Smudge202 2011-05-23 09:01:13

+0

添加v2.0时,您应该避免使用此属性,因为它是为了保持与v1.1应用程序的向后兼容性而添加的。 – 2011-06-21 15:33:30

0

你可以将这些东西作为扩展方法分开。这将允许你在对象本身中调用方法,而不是像现在这样将它作为参数传递。

所以,你可以这样做:label1.SetText("some text"); instad的SetText("some text", label1);

额外的增益是,你可以为每个控件类型不同的实现,所以你可以有一个标签和一个文本框。这将使代码更清洁。

最后,关于您使用反射设置属性的问题。您可以使用Type.GetProperty()方法获取对该属性的引用。这会返回一个PropertyInfo对象,您可以使用它来设置属性值,如下所示:

var textProperty = label1.GetType().GetProperty("Text"); 
textProperty.SetValue(label1, "some text", null); 
相关问题