如何避免Winform服务总线消息接收器中的跨线程操作无效异常 [英] How to avoid cross-thread operation not valid exception in Winform service bus message receiver

查看:48
本文介绍了如何避免Winform服务总线消息接收器中的跨线程操作无效异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

已经开发了运行良好的控制台应用程序.

Have developed an Azure service bus message receiver console app which is working fine console app.

控制台应用程序的代码如下:

Code for console app as follows:

using System.IO;
using Microsoft.ServiceBus.Messaging;

class Program
{
    static void Main(string[] args)
    {
        const string connectionString = "Endpoint=sb://sbusnsXXXX.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=bkjk3Qo5QFoILlnay44ptlukJqncoRUaAfR+KtZp6Vo=";
        const string queueName = "bewtstest1";
        var queueClient = QueueClient.CreateFromConnectionString(connectionString, queueName);

        try
        {
            queueClient.OnMessage(message => {
                string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();                                        
                Console.WriteLine(body);
                message.Complete();                    
            });
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            queueClient.OnMessage(message => {
                Console.WriteLine(ex.ToString());
                message.Abandon();                    
            });
            Console.ReadLine();
        }            
    }
}

已尝试转换为WinForms应用程序,因此我可以在列表框中将服务总线消息显示为字符串.
我用控制台应用程序代码创建了一个新的类( Azure ),并以主窗体调用该方法.

Have tried to convert to a WinForms app, so I can show the service bus message as a string in a ListBox.
I have created a new Class (Azure) with the console app code, and call the method in the main form.

Azure类:

using System.IO;
using Microsoft.ServiceBus.Messaging;

public class Azure
{
    public static void GetQueue(Form1 form)
    {
        const string connectionString = "Endpoint=sb://sbusnsXXXX.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=bkjk3Qo5QFoILlnay44ptlukJqncoRUaAfR+KtZp6Vo=";
        const string queueName = "bewtstest1";
        var queueClient = QueueClient.CreateFromConnectionString(connectionString, queueName);

        try
        {
            queueClient.OnMessage(message => {
                string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
                //Form1 f = new Form1();                
                form.listBox1.Items.Add(body);
                Console.WriteLine(body);
                message.Complete();
            });
            Console.ReadLine();
        }
        catch (Exception ex)
        {
            queueClient.OnMessage(message => {
                Console.WriteLine(ex.ToString());
                message.Abandon();
            });
            Console.ReadLine();
        }
    }
}

主表单:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Azure.GetQueue(this);
    }
}

代码会编译,但是当收到新的服务总线消息时,出现以下异常:

The code compiles, however when a new service bus message is received I get the following exception:

System.InvalidOperationException:'跨线程操作无效:从不是该线程的线程访问控件"listBox1"创建于".

System.InvalidOperationException: 'Cross-thread operation not valid: Control 'listBox1' accessed from a thread other than the thread it was created on.'

关于如何避免这种异常的任何想法(请注意,我尝试使用 InvokeRequired ,但无法编译代码)?

Any thoughts on how I can avoid this exception (note I have tried using InvokeRequired but can't get the code to compile)?

(感觉就像我关闭并重新运行程序时一样,该窗体在列表框中加载了消息,如下所示:

(Feel like I'm close as when I stop and re-run the program, the form loads with the message in the ListBox as shown here: listbox with message!)

推荐答案

当然,您不能从另一个线程引用在UI线程中创建的控件.如您所知,当您尝试执行以下操作时,会引发 Invalid Cross-thread operation 异常:Windows Forms应用程序必须是单线程的,其原因在

Of course you cannot reference a Control created in the UI Thread from another thread; as you have noticed, a Invalid Cross-thread operation exception is raised when you try to: a Windows Forms app must be single-threaded, the reasons are well explained in the STAThreadAttribute Class documentation.

注意:删除所有 Console.ReadLine() ,您不能在WinForms中使用它(没有控制台).

Note: Remove all Console.ReadLine(), you cannot use that in WinForms (there's no Console).

这里有一些可能对您有用的实现方式,按照您上下文中的相关性排序(嗯,至少我是这样认为的.您可以选择自己喜欢的方式.)

Here some implementations that may work for you, in order of relevance in your context (well, that's what I think, at least. You pick what you prefer).

Progress< T> :此类确实非常易于使用.您只需要定义其返回类型( T 类型,可以是任何东西,简单的 string ,类对象等).您可以就地定义它(在其中调用线程方法)并传递其引用.就这样.
接收引用的方法调用其 Report()方法,传递由 T 定义的值.
在创建 Progress< T> 对象的线程中执行此方法.
如您所见,您无需将Control引用传递给 GetQueue():

Progress<T>: this class is really simple to use. You just need to define its return type (the T type, it can be anything, a simple string, a class object etc.). You can define it in-place (where you call your threaded method(s)) and pass its reference. That's all.
The method that receives the reference calls its Report() method, passing the value(s) defined by T.
This method is executed in the Thread that created the Progress<T> object.
As you can see, you don't need to pass a Control reference to GetQueue():

表格端:

// [...]
var progress = new Progress<string>(msg => listBox1.Items.Add(msg));

Azure.GetQueue(progress);
// [...]

Azure课堂方面:

Azure class side:

public static void GetQueue(IProgress<string> update)
{    
    // [...]
    try {
        queueClient.OnMessage(message => {
            string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
            update.Report(body);
            message.Complete();
         });
    }
    // [...]
}


SynchronizationContext ( WindowsFormsSynchronizationContext) Post():此类用于同步线程上下文,其 Post()方法将异步消息调度到生成类对象的同步上下文,该对象由


► SynchronizationContext (WindowsFormsSynchronizationContext) Post(): this class is used to sync threading contexts, its Post() method dispatches an asynchronous message to the synchronization context where the class object is generated, referenced by the Current property.
Of course, see Parallel Computing - It's All About the SynchronizationContext.

实现与之前的实现没有太大不同:您可以将Lambda用作Post()方法的 SendOrPostCallback 委托.
Action< string> 委托用于发布到UI线程,而无需将Control引用传递给 Azure.GetQueue()方法:

The implementation is no much different than the previous: you can use a Lambda as the SendOrPostCallback delegate of the Post() method.
An Action<string> delegate is used to post to the UI Thread without the need to pass a Control reference to the Azure.GetQueue() method:

表格端:

// Add static Field for the SynchronizationContext object
static SynchronizationContext sync = null;

// Add a method that will receive the Post() using an Action delegate
private void Updater(string message) => listBox1.Items.Add(message);

// Call the method from somewhere, passing the current sync context
sync = SynchronizationContext.Current;
Azure.GetQueue(sync, Updater);
// [...]

Azure课堂方面:

Azure class side:

public static void GetQueue(SynchronizationContext sync, Action<string> updater)
{    
    // [...]
    try {
        queueClient.OnMessage(message => {
            string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
            sync.Post((spcb) => { updater(body); }, null);
            message.Complete();
         });
    }
    // [...]
}


Control.BeginInvoke():您可以使用 BeginInvoke()在创建控件句柄的线程上异步执行委托(通常作为Lambda).当然,您必须将Control引用传递给 Azure.GetQueue()方法.
因此,在这种情况下,此方法的优先级较低(但是您仍然可以使用它).


► Control.BeginInvoke(): you can use BeginInvoke() to execute a delegate (usually as a Lambda) asynchronously on the thread that created a Control's handle.
Of course, you have to pass a Control reference to the Azure.GetQueue() method.
That's why, in this case, this method has a lower preference (but you can use it anyway).

BeginInvoke()无需检查 Control.InvokeRequired :可以从任何线程(包括UI线程)调用此方法.调用 Invoke()则需要进行此检查,因为如果从UI线程使用它可能会导致死锁

BeginInvoke() doesn't require to check Control.InvokeRequired: this method can be called from any thread, including the UI Thread. Calling Invoke() instead requires that check, since it could cause a dead-lock if used from the UI Thread

表格端:

Azure.GetQueue(this, Updater);
// [...]

// Add a method that will act as the Action delegate
private void Updater(string message) => listBox1.Items.Add(message);

Azure课堂方面:

Azure class side:

public static void GetQueue(Control control, Action<string> action)
{    
    // [...]
    try {
        queueClient.OnMessage(message => {
            string body = new StreamReader(message.GetBody<Stream>(), Encoding.UTF8).ReadToEnd();
            control.BeginInvoke(new Action(()=> action(body));
            message.Complete();
         });
    }
    // [...]
}


您还可以使用 System.Windows.Threading.Dispatcher 管理线程的排队工作项,调用其 BeginInvoke()(首选)或 Invoke()方法.
它的实现与 SynchronizationContext 相似,其方法称为已提到的 Control.BeginInvoke()方法.


You can also use System.Windows.Threading.Dispatcher to manage a Thread's queued work items, calling its BeginInvoke() (preferable) or Invoke() methods.
Its implementation is similar to the SynchronizationContext one and its methods are called as the Control.BeginInvoke() method already mentioned.

我不在这里实现它,因为分派器需要引用 WindowsBase.dll (通常是WPF),这可能会在WinForms应用程序中导致不良效果那不是DpiAware.
您可以在这里阅读以下内容:
DPI意识-一个版本中不知道,另一个版本中不知道

I'm not implementing it here, since the Dispatcher requires a reference to WindowsBase.dll (WPF, usually) and this can cause undesired effects in a WinForms application that is not DpiAware.
You can read about this here:
DPI Awareness - Unaware in one Release, System Aware in the Other

无论如何,如果您有兴趣,请告诉我.

Anyway, in case you're interested, let me know.

这篇关于如何避免Winform服务总线消息接收器中的跨线程操作无效异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆