2013-08-22 61 views
0

在我們的應用程序中,我們有一個跟蹤窗口,我們可以在客戶端位置啓用一些調試功能,它被認爲是靜態庫。MTA線程中的日誌記錄窗口:訪問衝突

問題是,當有很多日誌消息進入窗口時,它會崩潰並出現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

由於'TraceView'似乎是一種形式,它爲什麼在MTA運行?你爲什麼叫'DoEvents'?什麼線程是從我這裏調用的'Write'?沒有足夠的信息。 – Jon

+0

我會考慮使用[OutputDebugString](http://www.pinvoke.net/default.aspx/kernel32.outputdebugstring)和[DebugView](http://technet.microsoft.com/en-au/sysinternals/bb896647 .aspx)之前去一個自定義的UI路由。 – Noseratio

+0

我認爲訪問衝突是由重入造成的。在先前調用uiTrace.AppendText之前,可能會從另一個線程*調用'Write',最終再次重新進入uiTrace.AppendText。檢查我的答案,找出解決這個問題的辦法。 – Noseratio

回答

0

下面是我對你的記錄器的修改。請注意如何使用_processinglock來避免重入並保護_queue。另外,我使用SynchronizationContext而不是Control.BeginInvoke來避免對窗口配置狀態的依賴。 TraceView可以創建(使用TraceView.Create)並可以從任何線程使用,但其窗口屬於parent窗口的線程,這也是將文本發送到richedit的地方。有可能有一個專用的STA線程,但我不認爲這是必要的。

將帖子我已經消除了什麼可能是在檢查_processing競爭條件和情況下添加CreateOnOwnThread一個專門的線程記錄程序UI是一個要求。我還決定保留Application.DoEvents()的情況下,當從緊密循環調用Write時,以保持UI的響應。

用途(壓力測試):

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; 
     } 

    } 
} 
+0

感謝堆,這是一個很好的參數,讓項目更新到.NET 4.0 – TheRealTy

+0

不用擔心,很高興它有所幫助。這個代碼應該與.NET 3.5兼容,除了'ManualResetEventSlim'(你可以直接使用'ManualResetEvent')。我無法想象其他任何特定於4.5的東西。在附註中,您可能想要堅持'CreateOnOwnThread'的使用方式,通過'Application.DoEvents()'中的窗口消息來避免主UI線程上潛在的代碼重入。另外,只要刪除'DoEvents',但是如果有大量的日誌記錄,它可能會使主UI的響應性降低。 – Noseratio

0

我有VB比C#有更多的.Net經驗,但不下面的代碼:

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

結果gui如果InvokeRequired等在If聲明,以及正在被調用在gui()的當前(可能是非UI)線程中執行(再次)。

豈不:

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

更合適?

+1

您添加的'else'不會改變任何內容,因爲'if'塊中有'return'。以下將更加正確:'if(!uiTrace.Disposing &&!uiTrace.IsDisposed){if(uiTrace.InvokeRequired)uiTrace.BeginInvoke(gui);否則gui(); }'當控件處理時應該沒有更多的日誌記錄(這可能是崩潰的原因,BTW)。 此外,日誌線程有一個UI對象,必須是STA('_runningThread.SetApartmentState(ApartmentState.STA)'),正如@Jon指出的那樣。這就是說,爲什麼有一個單獨的線程,而不是使用'父'形式的? – Noseratio

+1

Noseratio,你說得很對,我忽略了'Return'。 –