Выполнить делегата в потоке пользовательского интерфейса (используя помпу сообщений)

У меня есть фоновый поток, который обрабатывает связь с внешней службой. Каждый раз, когда фоновый поток получает сообщение, я хотел бы передать его потоку пользовательского интерфейса для дальнейшей обработки (отображения пользователю).

В настоящее время я создал потокобезопасную очередь сообщений, которая периодически объединяется в Timer.Tick и заполняется в фоновом потоке. Но это решение не оптимально.

Вы знаете, как использовать помпу сообщений для передачи событий из фонового потока в поток пользовательского интерфейса?


person Piotr Czapla    schedule 15.07.2009    source источник
comment
winforms, но я хотел бы услышать об обоих   -  person Piotr Czapla    schedule 15.07.2009


Ответы (5)


Есть несколько приемов.

  1. Control.Invoke() (и др.)

    Я обнаружил, что этот метод winforms неизменно прост в использовании, но имейте в виду, что есть некоторые тонкие правила, которые вам нужно понять. Я попытался создать общую рабочую реализацию, которая должным образом обрабатывает правила в сегменте кода, который у меня опубликовано в другом месте на stackoverflow.

  2. SynchronizationContext

    Мне не нужно было много использовать эту технику, поэтому я не могу сказать о ней ничего значимого. Однако вы должны знать, что он существует. Я считаю, что это эффективный способ гарантировать, что что-то будет вызвано в контексте конкретного потока, даже если этот поток не является потоком пользовательского интерфейса.

  3. _3 _

    Если вы работаете с WPF, элементы управления WPF обычно происходят от DispatcherObject для предоставления объекта Dispatcher. Это более многофункциональный метод синхронизации, чем Control.Invoke(), но также более сложный. Обязательно внимательно прочтите документацию.

person Greg D    schedule 15.07.2009
comment
Ваши методы расширения заставили меня задуматься. Спасибо. - person Piotr Czapla; 15.07.2009

Вы можете использовать Control.Invoke и использовать делегат. Делегат будет выполнен в потоке, создавшем элемент управления.

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

person Kevin Doyon    schedule 15.07.2009
comment
Просто убедитесь, что вы не путаете Delegate.Invoke () с Control.Invoke () ... Control.Invoke () - это то, что вы хотите - person STW; 15.07.2009

Если ваш поток графического интерфейса заблокирован и не обрабатывает никаких сообщений, вы можете использовать Application.DoEvents, чтобы заставить поток графического интерфейса обработать все ожидающие сообщения в этом потоке.

Чтобы перекачивать сообщения в поток Control, вы, конечно, можете использовать методы Control.BeginInvoke или Control.Invoke, но обратите внимание, что Control.Invoke будет блокироваться, если поток-владелец Control в настоящее время блокируется.

person Mike J    schedule 15.07.2009
comment
Application.DoEvents - плохой запах кода. Я никогда не видел, чтобы его использовал хорошо разработанный код. - person Greg D; 15.07.2009
comment
Какие методы вы использовали, чтобы заставить сообщения потока продолжать перекачку в поток графического интерфейса пользователя, когда основной графический интерфейс заблокирован? - person Mike J; 15.07.2009

Вы также можете использовать WPF Dispatcher (класс Dispatcher) в WindowsBase.dll.

person Nicolas Dorier    schedule 15.07.2009

Вот пример использования объекта Dispacther в WPF с 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>
person Nikola Stjelja    schedule 29.06.2010