程序在访问消息队列时挂起

时间:2013-12-31 10:43:26

标签: c# .net multithreading msmq enterprise-library

我有一个线程数组,它们从私有消息队列中检索消息,将它们反序列化为日志条目对象,并将日志条目对象的属性存储在SQL Server数据库表中。

这是我创建和启动线程的代码。

        try
        {
            for (int i = 0; i < threads.Length; i++)
            {
                threads[i] = new Thread(new ThreadStart(this.logEntriesToDatabase));
                threads[i].Start();
            }
        }
        catch (ThreadStateException ex)
        {
            MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK,MessageBoxIcon.Error);
            return;
        }
        catch (OutOfMemoryException ex)
        {
            MessageBox.Show("Not Enough Memory Please Close Other Applications To Continue", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

每个线程执行一个函数logentriestodatabase()

while(true)
        {
            #region Retrieves Message from Message Queue and Deserialized it to a Log Entry Object.

                #region Sleep Time for Current Thread
                    Thread.Sleep(180);
                #endregion
                #region Check to See Whether Queue Is Empty. If so go back to start of while loop
                    if (q1.GetAllMessages().Length == 0)
                    {
                        continue;
                    }
                #endregion
                #region Message retrieval and Deserialization Code
                    System.Messaging.Message m = this.q1.Receive();
                    m.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
                    LogEntry lg = BinaryLogFormatter.Deserialize(m.Body.ToString());
                #endregion

            #endregion

            #region Insert Log Entry Into Database

                #region Define a new SQL Connection with username and password specified in App.Config, an SQL Transaction and database queuries

                    SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["LogReader"].ConnectionString);
                    SqlTransaction transaction;
                    string query_insert_into_logentry = "INSERT INTO logentry" + "(message, priority, processname, severity, accountid, ipaddress, servername, servertype, timestamp)" + "VALUES ('" + lg.Message + "'," + lg.Priority + ",'" + lg.AppDomainName + "','" + lg.Severity.ToString() + "','" + lg.ExtendedProperties["AccountID"].ToString() + "','" + lg.ExtendedProperties["IpAddress"].ToString() + "','" + lg.ExtendedProperties["ServerName"].ToString() + "','" + lg.ExtendedProperties["ServerType"].ToString() + "','" + lg.TimeStamp.ToString() + "')";
                    string query_insert_into_category = "INSERT INTO category (category) VALUES ('" + lg.Categories.First().ToString() + "')";

                #endregion
                #region Begin and Terminates Transaction and Closes the SQL Connection Catches any SQL Exception Thrown and Displays Them

                    try
                    {
                        conn.Open();
                        transaction = conn.BeginTransaction();
                        new SqlCommand(query_insert_into_logentry, conn, transaction).ExecuteNonQuery();
                        new SqlCommand(query_insert_into_category, conn, transaction).ExecuteNonQuery();
                        transaction.Commit();
                        conn.Close();
                    }
                    catch (SqlException ex)
                    {
                        MessageBox.Show(ex.Message);
                        return;
                    }

                #endregion
            #endregion
        }

现在每当我运行此程序时,消息队列变空,程序就会挂起。 我似乎无法弄清楚为什么。我试图给q1.Receive()函数一个TimeSpan,但是没有用。我用180毫秒的时间调用了睡眠方法,但它仍然不起作用。也许是因为q1.Receive方法在遇到空队列时将当前线程发送到阻塞状态。

请帮助我接受我的想法。

2 个答案:

答案 0 :(得分:1)

您可以使用MessageQueue.BeginReceive / EndReceive异步读取消息,而不是在紧密循环中同步读取消息并阻塞多个线程。提出了类似的问题here

如果您使用的是.NET 4.0或更高版本,则可以从BeginReceive / EndReceive对创建一个Task,并使用ContinueWith处理该消息,而无需创建新线程。在.NET 4.5中,您可以使用asyc/await关键字使处理更简单,例如:

private async Task<Message> MyReceiveAsync()
{
    MessageQueue queue=new MessageQueue();
    ...
    var message=await Task.Factory.FromAsync<Message>(
                       queue.BeginReceive(),
                       queue.EndReceive);

    return message;
}

public async Task LogToDB()
{
    while(true)
    {
       var message=await MyReceiveAsync();
       SaveMessage(message);
    }
}

即使LogToDB使用`while(true),循环也会异步执行。

要结束循环,您可以将CancellationToken传递给LogToDBend processing cooperatively

public async Task LogToDB(CancellationToken token)
{
    while(!token.IsCancellationRequested)
    {
       var message=await MyReceiveAsync();
       SaveMessage(message);
    }
}

这样可以避免创建多个线程和计时器。

答案 1 :(得分:0)

break为空时,您可能需要continue循环而不是queuecontinue导致无限循环,导致无响应行为。

更改

if (q1.GetAllMessages().Length == 0)
{
     continue;
}

if (q1.GetAllMessages().Length == 0)
{
     break;
}
根据评论

修改

您可以通过放置Thread.Sleep以允许其他线程获取CPU共享来为当前线程提供中断。我不会在这里使用Sleep,而是使用计时器System.Timers.Timer,并在固定的时间间隔后对MSMQ执行操作。

您可以使用MSMQEvent.Arrived事件代替计时器。

使用计时器。

void SumFun()
{
    aTimer = new System.Timers.Timer(10000);        
    aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    aTimer.Interval = 2000;
    aTimer.Enabled = true;
}    

活动宣言。

private static void OnTimedEvent(object source, ElapsedEventArgs e)
{ 
    //Call the method for process MSMQ and do not use While loop to check queue.       
}