在 ui 线程中执行委托(使用消息泵)

Posted

技术标签:

【中文标题】在 ui 线程中执行委托(使用消息泵)【英文标题】:Execute a delegate in the ui thread (using message pump) 【发布时间】:2010-11-11 01:46:55 【问题描述】:

我有一个后台线程来处理与外部服务的通信。每次后台线程收到消息时,我都想将其传递给 UI 线程进行进一步处理(显示给用户)。

目前我已经创建了一个线程安全的消息队列,它定期在 Timer.Tick 中汇集并填充到后台线程中。但是这个解决方案是次优的。

你知道如何使用消息泵将事件从后台线程传递到 ui 线程吗?

【问题讨论】:

winforms 但我想听听这两种情况 【参考方案1】:

您可以使用 Control.Invoke 并使用委托。委托将在创建控件的线程上执行。

http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx

【讨论】:

只要确保不要混淆 Delegate.Invoke() 和 Control.Invoke()... Control.Invoke() 就是你想要的【参考方案2】:

有一些技巧。

    Control.Invoke()(等)

    我发现这种 winforms 技术始终易于使用,但请注意,您需要掌握一些微妙的规则才能正确使用。我试图捕获一个通用的、有效的实现,它可以正确处理我 posted elsewhere on *** 的代码段中的规则。

    SynchronizationContext

    我不需要太多使用这种技术,所以我真的不能说任何有意义的东西。但是,您应该知道它存在。我相信这是确保在特定线程的上下文中调用某些内容的有效方法,即使该线程不是 ui 线程。

    DispatcherObject.Dispatcher

    如果您使用 WPF,则 WPF 控件通常会派生自 DispatcherObject 以提供 Dispatcher 对象。这是一种比Control.Invoke() 功能更丰富的同步技术,但也更复杂。请务必仔细阅读文档。

【讨论】:

你的扩展方法给了我很多思考。谢谢。【参考方案3】:

如果您的 GUI 线程已阻塞且未处理任何消息,您可以使用 Application.DoEvents 强制 GUI 线程处理该线程上的所有等待消息。

要将消息泵送到 Control 的线程,当然可以使用 Control.BeginInvokeControl.Invoke 方法,但请注意,如果 Control 的所属线程当前正在阻塞,Control.Invoke 将阻塞。

【讨论】:

Application.DoEvents 是一种糟糕的代码气味。我从未见过精心设计的代码使用它。 当主 GUI 阻塞时,您使用了哪些技术来强制线程消息继续发送到 GUI 线程?【参考方案4】:

您也可以在 WindowsBase.dll 中使用 WPF Dispatcher(类 Dispatcher)。

【讨论】:

【参考方案5】:

这是一个在 WPF 中将 Dispacther 对象与 MSMQ 一起使用的示例。

背后的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Messaging;

namespace MSMQGui

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    
        private string queueName = @".\private$\MyFunWithMSMQ";
        private MessageQueue queue = null;

        public MainWindow()
        
            InitializeComponent();

            if (!MessageQueue.Exists(queueName))
                MessageQueue.Create(queueName,false);


           queue = new MessageQueue(queueName);
           queue.ReceiveCompleted += receiveCompleted;

        

        private void btnAddMessage_Click(object sender, RoutedEventArgs e)
        
            string message = txtMessage.Text;
            txtMessage.Text = String.Empty;

            queue.Send(message);

            MessageBox.Show("Message :" + message + " sent");
        

        private void Populate(object sender, RoutedEventArgs e)
        
            try
            
                queue.BeginReceive(TimeSpan.FromSeconds(1)) ;     
            
            catch (MessageQueueException)
            
                MessageBox.Show("No message available");
            
        

        private void receiveCompleted(object source, ReceiveCompletedEventArgs e)
        
            try
            
                var message=queue.EndReceive(e.AsyncResult);



                Action<string> addMessage= (string msg) => 
                     ListViewItem item = new ListViewItem();
                     item.Content = msg;
                     lsvMessages.Items.Add(item);
                ;

                this.Dispatcher.Invoke(addMessage, message.Body as string);
            
            catch (MessageQueueException)
            
                MessageBox.Show("No message available");
            
        
    

XAML:

<Window x:Class="MSMQGui.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"></ColumnDefinition>
            <ColumnDefinition Width="3*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="1*"></RowDefinition>
            <RowDefinition Height="9*"></RowDefinition>      
            <RowDefinition Height="1*"></RowDefinition>
        </Grid.RowDefinitions>

        <!-- First row -->
        <Label x:Name="lblMessage" 
               Content="Message:" 
               HorizontalAlignment="Stretch" 
               VerticalAlignment="Top" 
               HorizontalContentAlignment="Right"
               Grid.Column="0" Grid.Row="0"
               ></Label>
        <StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal">
        <TextBox x:Name="txtMessage"  Width="200" HorizontalAlignment="Left" ></TextBox>
        <Button x:Name="btnAddMessage"  Content="Add message" Margin="5,0,0,0" Click="btnAddMessage_Click"></Button>
        </StackPanel>

        <!-- Second row -->
        <ListView x:Name="lsvMessages" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,5,0,0">
        </ListView>

        <!-- Third row-->
        <Button x:Name="btnPopulate" Grid.Column="1" Grid.Row="2" HorizontalAlignment="Right" Click="Populate" Content="Get messages from queque" Margin="5,0,0,0"></Button>

    </Grid>
</Window>

【讨论】:

以上是关于在 ui 线程中执行委托(使用消息泵)的主要内容,如果未能解决你的问题,请参考以下文章

WINAPI - 我想让消息泵在一个单独的线程中进行

C#如何回到主线程,如何在委托指定线程执行

如何将消息发布到运行消息泵的 STA 线程?

跨线程操作Treeview

消息循环是不是在 UI 线程上执行?

主线程不能执行耗时的操作,子线程不能更新Ui