如何避免在 C# 中完成线程?

Posted

技术标签:

【中文标题】如何避免在 C# 中完成线程?【英文标题】:How to avoid a thread being completed in C#? 【发布时间】:2019-05-26 06:17:07 【问题描述】:

我有一个用 C# 编写的 RabbitMQ 客户端程序。虽然应用程序在基于控制台的应用程序中工作(因为执行被 Console.ReadLine 阻止),但它在基于 Windows 窗体的应用程序中不起作用。在 Windows 窗体应用程序中,执行不会等待 Console.ReadLine 并在完成时终止。我正在寻找解决方案,让我的听众不断监视来自服务器的新消息而不会被终止。 这是客户端代码:

    try 
            var factory = new ConnectionFactory()  HostName = "xxx" , UserName ="xxx", Password="xxx";
            using(var connection = factory.CreateConnection())
            using(var channel = connection.CreateModel())
            
                channel.ExchangeDeclare(exchange: "call_notify", type: "fanout");

                var queueName = channel.QueueDeclare().QueueName;
                channel.QueueBind(queue: queueName,
                                  exchange: "call_notify",
                                  routingKey: "");

                var consumer = new EventingBasicConsumer(channel);
                consumer.Received += (model, ea) =>
                
                    var body = ea.Body;
                    var message = Encoding.UTF8.GetString(body);
                    Console.WriteLine(message);
                ;
                channel.BasicConsume(queue: queueName,
                                     autoAck: true,
                                     consumer: consumer);

                Console.WriteLine(" Press [enter] to exit.");
                Console.ReadLine();  // Program does'nt wait here in windows form based application
            
        

【问题讨论】:

Windows 应用程序和控制台应用程序并不相互排斥... 在 Winforms 应用程序中单击主窗口的关闭按钮相当于在控制台模式应用程序中按 Enter。不清楚你为什么点击它。如果您不创建 Form 对象并且只使用 Application.Run() 而不使用参数,那么它将永远运行并且只能通过任务管理器终止。您应该将其作为一项服务的可能性很高。 【参考方案1】:
    不要使用using,因为这会立即处理所有内容 将所需的对象(连接?通道?消费者?)存储在类字段而不是局部变量中 不需要线程,因为对象是异步处理的。只需创建对象即可 在应用程序终止或您需要停止监听时关闭/处置对象

这样它们将一直存在,直到应用程序终止。

【讨论】:

正确,我同意,但是这个地址如何控制台和winforms?在这两种解决方案中,您如何等待? 在控制台应用程序中,如果用户删除了 readline,控制台应用程序会立即退出,但 winforms 它不起作用...This way they'll live until the application terminates我不同意,这取决于这些对象的实现.. . @Çöđěxěŕ 不,这些对象将继续存在。他们不能被摧毁,因为有什么东西抓住了他们。当然,连接可能不会,但那是另一回事。并且没有任何关于控制台的问题,因为已经处理了。在那里也可以使用相同的代码,除了不结束应用程序,他们知道该怎么做。无需等待,只需让对象保持活力即可。 为了更有意义,我创建了一个 UI,并在 MainUIForm 构造函数中调用此方法,该方法创建一个连接并侦听传入消息。不知何故,这不起作用。看起来连接被破坏了【参考方案2】:

如果您希望代码在两个平台上都运行,最好创建一个抽象层来公开消息并处理启动/停止逻辑。

public class RabbitMQManager : IDisposable

    private bool _disposed = false;
    private IModel _channel;
    private IConnection _connection;

    public event EventHandler<string> MessageReceived;

    protected virtual void Dispose(bool disposing)
    
        if (!_disposed)
        
            if (disposing)
            
                _channel?.Dispose();
                _connection?.Dispose();
            

            _disposed = true;
        
    

    public void Dispose()
    
        Dispose(true);
    

    public void Connect()
    
        var factory = new ConnectionFactory  HostName = "xxx", UserName = "xxx", Password = "xxx" ;
        _connection = factory.CreateConnection();
        _channel = _connection.CreateModel();

        _channel.ExchangeDeclare(exchange: "call_notify", type: "fanout");

        string queueName = _channel.QueueDeclare().QueueName;
        _channel.QueueBind(queue: queueName,
                          exchange: "call_notify",
                          routingKey: "");

        var consumer = new EventingBasicConsumer(_channel);
        consumer.Received += (model, ea) =>
        
            byte[] body = ea.Body;
            string message = Encoding.UTF8.GetString(body);
            MessageReceived?.Invoke(this, message);
        ;

        _channel.BasicConsume(queue: queueName,
                             autoAck: true,
                             consumer: consumer);
    

然后,您可以通过创建该类的实例并订阅MessageReceived 事件,在您想要的任何项目类型中使用它。例如,WinForms 实现将是:

public class MyForm : Form

    private RabbitMQManager _rabbitMQManager;

    public MyForm()  _rabbitMQManager = new RabbitMQManager(); 

    // you can call this from constructor or some event
    public void Connect()
    
        _rabbitMQManager.MessageReceived = (sender, msg) => someLabel.Text = msg;
        _rabbitMQManager.Connect();
    

查看此问答以了解如何覆盖 MyForm.Dispose 以便正确处置资源:How do I extend a WinForm's Dispose method?

在控制台应用程序中,这可能只是:

using (var manager = new RabbitMQManager())

    manager.MessageReceived += (sender, msg) => Console.WriteLine(msg);
    manager.Connect();
    Console.Read();

【讨论】:

【参考方案3】:

谢谢大家!根据您的所有建议,我找到了多种方法来实现这一目标。

我实现这一点的方式是将工厂、连接和通道作为类变量并在 MainForm 构造函数中定义它们。这样就可以保留对象,并且程序会继续侦听传入的消息。

【讨论】:

以上是关于如何避免在 C# 中完成线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免计时器滴答中断主线程?

如何避免 NoRouteToHostException?

C#中Lock的秘密

c#中Lock的秘密

避免顺序线程中的冲突

在执行相同功能时如何避免 C# 中的短路评估