MTA线程中的日志记录窗口:访问冲突

时间:2013-08-22 23:28:28

标签: c# .net multithreading winforms thread-safety

在我们的应用程序中,我们有一个跟踪窗口,我们可以在客户端位置启用以允许一些调试,它被认为是静态库。

问题是,当有大量日志消息进入窗口时,它会因AccessViolation错误而崩溃。崩溃的代码链接是RichTextBox.AppendText(..,..,..)。

这是我们创建窗口的地方。

public static void Start(Form parent)
{
  if (_runningThread == null || !_runningThread.IsAlive)
  {

      _runningThread = new Thread(() =>
          {
              _traceView = new TraceView(parent) { Text = "Tracing ~ " + parent.Text };
              Application.Run(_traceView);

          });

      _runningThread.SetApartmentState(ApartmentState.MTA);
      _runningThread.Start();
  }

}

这是我们在文本框中写一行

public void Write(string line, Color color)
{
  try
  {
      _msgQueue.Enqueue(new Tuple<string, Color>(line, color));

      MethodInvoker gui = delegate
          {
              try
              {
                  // Was getting an overflow so trim out some lines
                  if (uiTrace.Lines.Length > 5000)
                  {
                      uiTrace.Lines = new string[0];
                      uiTrace.SelectionStart = uiTrace.TextLength;
                      Application.DoEvents();
                  }

                  while (_msgQueue.Count != 0)
                  {

                      bool retry;
                      var count = 0;
                      do
                      {
                          try
                          {
                              count++;
                              if (_indent < 0)
                                  _indent = 0;

                              var msg = _msgQueue.Dequeue();
                              var selectionStart = uiTrace.TextLength;
                              uiTrace.AppendText(string.Format("[{0}] {1}{2}", _stopwatch.ElapsedMilliseconds, string.Empty.PadLeft(_indent * 4), msg.Item1));


                              uiTrace.Select(selectionStart, uiTrace.TextLength);
                              uiTrace.SelectionColor = msg.Item2;
                              uiTrace.SelectionStart = uiTrace.TextLength;
                              uiTrace.ScrollToCaret();
                              retry = false;
                          }
                          catch (Exception)
                          {
                              retry = true;
                          }
                      } while (retry && count < 5);
                  }
              }
              catch (Exception)
              {
                  // We don't care about exceptions in here, for now anyway
              }
          };

      if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
      {
          uiTrace.BeginInvoke(gui);
          return;
      }
      gui();
  }
  catch (Exception)
  {

   //   QIT_Backoffice.Processes.Errors.ErrorHandler.WriteErrorLog(Sources.SourceEnum.External, ex, "Error writing to trace");
  }
}

我真的不知道如何解决这个问题,我认为调用BeginInvoke()就是需要的。

寻找任何可能的帮助,或者如果有人知道可以更好地处理这个问题的第三方工具,我很高兴看到这一点。

2 个答案:

答案 0 :(得分:0)

我对VB的体验比C#更多。但是没有以下代码:

  if (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
  {
      uiTrace.BeginInvoke(gui);
      return;
  }
  gui();

导致在gui语句中调用InvokeRequiredIf,以及在{{当前(可能是非UI)线程中执行(再次)gui() 1}}。

岂不:

  If (uiTrace.InvokeRequired && !uiTrace.Disposing && !uiTrace.IsDisposed)
  {
      uiTrace.BeginInvoke(gui);
      return;
  }
  Else
      gui();

更合适吗?

答案 1 :(得分:0)

以下是我对记录器的修改。请注意_processinglock如何用于避免重入和保护_queue。此外,我使用SynchronizationContext而不是Control.BeginInvoke来避免对窗口处置状态的任何依赖。可以创建TraceView(使用TraceView.Create)并在任何线程中使用parent,但是它的窗口属于richedit窗口的线程,并且它也是将文本传递到_processing的位置。可以为此设置一个专用的STA线程,但我觉得没必要。

[已编辑] 我已经消除了检查CreateOnOwnThread时可能存在竞争条件的情况,并添加了Application.DoEvents(),以防需要记录器UI的专用线程。我还决定在紧急循环中调用Write的情况下保留private void Form1_Load(object sender, EventArgs ev) { var traceView = TraceView.Create(this); for (var i = 0; i < 1000; i++) { var _i = i; Task.Run(() => { traceView.Write(String.Format("Line: {0}\n", _i), System.Drawing.Color.Green); }); } } ,以保持用户界面的响应。

用法(压力测试):

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Logger
{
    public partial class TraceView : Form
    {
        private Form _parent = null;

        private SynchronizationContext _context = SynchronizationContext.Current;
        private int _threadId = Thread.CurrentThread.ManagedThreadId;

        private object _lock = new Object(); // sync lock to protect _queue and _processing
        private Queue<Tuple<string, Color>> _queue = new Queue<Tuple<string, Color>>();
        private volatile bool _processing = false; // reentracy check flag

        public TraceView(Form parent)
        {
            _parent = parent;
            InitializeComponent();
        }

        public static TraceView Create(Form parent)
        {
            TraceView view = null;
            // create it on the parent window's thread
            parent.Invoke(new Action(() => {
                view = new TraceView(parent);
                view.Show(parent);
            }));
            return view;
        }

        private void DequeueMessages()
        {
            // make sure we are on the UI thread
            Debug.Assert(Thread.CurrentThread.ManagedThreadId == _threadId); 

            lock (_lock)
            {
                // prevent re-entracy
                if (_processing)
                    return;
                // mark the beginning of processing
                _processing = true;
            }

            // process pending messages
            for (; ; )
            {
                Tuple<string, Color> msg = null;

                lock (_lock)
                {
                    if (!_queue.Any())
                    {
                        // mark the end of processing
                        _processing = false;
                        return;
                    }
                    msg = _queue.Dequeue();
                }

                if (this.Disposing || this.IsDisposed)
                {
                    // do not just loose messages if the window is disposed
                    Trace.Write(msg.Item1); 
                }
                else
                {
                    var selectionStart = _richTextBox.TextLength;
                    _richTextBox.AppendText(msg.Item1);
                    _richTextBox.Select(selectionStart, _richTextBox.TextLength);
                    _richTextBox.SelectionColor = msg.Item2;
                    _richTextBox.SelectionStart = _richTextBox.TextLength;
                    _richTextBox.ScrollToCaret();
                    _richTextBox.Refresh(); // redraw;
                    // DoEvents is required if logging from a tight loop, 
                    // to keep the UI responsive
                    Application.DoEvents(); 
                }
            }
        }

        public void Write(string line, Color color)
        {
            lock (_lock)
            {
                _queue.Enqueue(new Tuple<string, Color>(line, color));
                // prevent re-entracy
                if (_processing)
                    return; // DequeueMessages is already in progress
            }

            if (Thread.CurrentThread.ManagedThreadId == _threadId)
                DequeueMessages();
            else
                _context.Post((_) => 
                { 
                    DequeueMessages();  
                }, null);
        }

        public static TraceView CreateOnOwnThread()
        {
            TraceView view = null;
            using (var sync = new ManualResetEventSlim())
            {
                // create it on its own thread
                var thread = new Thread(() =>
                {
                    SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
                    view = new TraceView(null);
                    view.Show();
                    sync.Set(); // ready Write calls
                    Application.Run(view); // view does Application.ExitThread() when closed
                    return;
                });

                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                sync.Wait();
            }
            return view;
        }

    }
}

实现:

{{1}}