C# 如何避免Winform服务总线消息接收器中的跨线程操作无效异常

C# 如何避免Winform服务总线消息接收器中的跨线程操作无效异常,c#,winforms,azure,servicebus,ui-thread,C#,Winforms,Azure,Servicebus,Ui Thread,已经开发了Azure service bus消息接收器控制台应用程序,该应用程序运行良好 控制台应用程序的代码如下: using System.IO; using Microsoft.ServiceBus.Messaging; class Program { static void Main(string[] args) { const string connectionString = "Endpoint=sb://sbusnsXXXX.servicebus.

已经开发了Azure service bus消息接收器控制台应用程序,该应用程序运行良好

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

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();
        }            
    }
}
主要形式:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Azure.GetQueue(this);
    }
}
代码会编译,但是当收到新的服务总线消息时,我会得到以下异常:

System.InvalidOperationException:'跨线程操作无效: 控件“listBox1”是从其所在线程以外的线程访问的 创建于。”

关于如何避免此异常的想法(注意,我尝试使用
invokererequired
,但无法编译代码)


(感觉很接近,因为当我停止并重新运行程序时,表单会加载列表框中的消息,如下所示:!)

当然,您不能从另一个线程引用在UI线程中创建的控件;正如您所注意到的,当您尝试执行以下操作时,会引发一个
无效的跨线程操作
异常:Windows窗体应用程序必须是单线程的,文档中详细解释了原因

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

这里有一些可能适合您的实现,按照与您的上下文相关的顺序排列(好吧,至少我是这么认为的。您可以选择您喜欢的)

► : 这个类的使用非常简单。您只需要定义它的返回类型(
T
type,它可以是任何东西,一个简单的
string
,一个类对象等等)。您可以就地定义它(在这里调用线程方法)并传递其引用。就这些。
接收引用的方法调用其方法,传递由
T
定义的值
此方法在创建
Progress
对象的线程中执行。
如您所见,您不需要将控件引用传递给
GetQueue()

形式方面:

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

Azure.GetQueue(progress);
// [...]
// 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.GetQueue(this, Updater);
// [...]

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

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();
         });
    }
    // [...]
}
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();
         });
    }
    // [...]
}
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();
         });
    }
    // [...]
}
Azure类端:

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();
         });
    }
    // [...]
}
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();
         });
    }
    // [...]
}
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();
         });
    }
    // [...]
}
publicstaticvoidgetqueue(控制、操作)
{    
// [...]
试一试{
queueClient.OnMessage(消息=>{
string body=newstreamreader(message.GetBody(),Encoding.UTF8.ReadToEnd();
control.BeginInvoke(新动作(()=>动作(主体));
message.Complete();
});
}
// [...]
}

您还可以使用来管理线程的排队工作项,调用其
BeginInvoke()
(首选)或
Invoke()
方法。
它的实现类似于
SynchronizationContext
one,其方法称为前面提到的
Control.BeginInvoke()
方法

我没有在这里实现它,因为Dispatcher需要对
WindowsBase.dll
(通常是WPF)的引用,这可能会在非DpiAware的WinForms应用程序中造成不希望的效果。
您可以在这里阅读:


无论如何,如果您感兴趣,请告诉我。

谢谢Jimi,非常感谢您的详细回复!!有很多非常好的信息需要查看和消化。我刚刚用上面的代码尝试了进度选项,但在更新(正文)时出现编译错误;GetQueue中的一行写着CS0149方法名称。对此有什么想法吗?谢谢Jimi!效果很好。非常感谢您花这么多时间回答如此全面的问题-为我节省了很多时间和挫折。