开发者

Thread safe logging Form

I have Form responsible for logging all neccessary informations from all thread into its richTextBox. I use Invoke when I want to access its controls from diff开发者_运维知识库rent threads (to append text to richTextBox) and it works perfectly.

But I need this log form to be shown when first message is going to be append to richTextBox and I won't know which from my threads will do this first. Also when I close log form, I want it to be shown again when next message comes (and in this case I still won't know which thread will call it first).

I tried to create this form in new threads and via Application.Run(ApplicationContext) but none of this solutions worked.

Do you have any hints?


Rather than closing/respawning windows, it's often easier to simply hide and unhide.

Create the window whenever you create your main window and never close it. Add an onbeforeclose (or whatever that was called again...) event handler that cancels a user-initiated close and instead hides the window.

Now, you're already using a thread-safe dispatcher to modify the window contents: simply add one line to that event handler to unhide the window (this won't do anything if it's already visible), and you're good to go!

Incidentally, a useful thing to have in these scenario's is a custom TextWriter subclass. You can make one as follows:

public abstract class AbstractTextWriter : TextWriter {
    protected abstract void WriteString(string value);
    public override Encoding Encoding { get { return Encoding.Unicode; } }
    public override void Write(char[] buffer, int index, int count) {
        WriteString(new string(buffer, index, count));
    }
    public override void Write(char value) {
        WriteString(value.ToString(FormatProvider));
    }
    public override void Write(string value) { WriteString(value); }
    //subclasses might override Flush, Dispose and Encoding
}
public class DelegateTextWriter : AbstractTextWriter {
    readonly Action<string> OnWrite;
    readonly Action OnClose;
    static void NullOp() { }
    public DelegateTextWriter(Action<string> onWrite, Action onClose = null) { 
        OnWrite = onWrite; 
        OnClose = onClose ?? NullOp; 
    }
    protected override void WriteString(string value) { OnWrite(value); }
    protected override void Dispose(bool disposing) { 
        OnClose(); base.Dispose(disposing); 
    }
}

That way you can stick your form-updating logic into something like this...

var threadSafeLogWriter =  new DelegateTextWriter(str => {
        Action updateCmd = ()=>{
            myControl...//append text to whatever control here
            myControl.Show();
        };
        if(myControl.InvokeRequired) myControl.BeginInvoke(updateCmd);
        else updateCmd();
    });

... and use it everywhere, potentially even doing Console.SetOut to catch output to Console.Write. It's ok to call Write on this TextWriter on multiple threads since the implementation is self-synchronizing.

You could test it like this...

Console.SetOut(threadSafeLogWriter);
Parallel.For(0,100, i=>{
    threadSafeLogWriter.Write("Hello({0}) ", i);
    Thread.Yield();
    Console.WriteLine("World({0})!",i);
});

If you do lots of logging of small messages, you may end up with a lot of BeginInvoke calls, and these are slow. As an optimization, you might instead enqueue all log messages to a ConcurrentQueue or some other synchronized structure, and only BeginInvoke if the queue was empty before you enqueued to it. That way you usually do only one BeginInvoke between UI updates; and when the UI gets around to doing it's thing, it clears the flag and then appends all queued text at once. However, since all threads doing logging just dump their messages in whatever order they come into the logger, it can be very bad for readability to have lots of small log statements; best to log as large a string as possible at once to ensure it's not interrupted by another threads message; and if you do that then you won't have many BeginInvoke's anyhow and perf. will be less of an issue.


Similar to this other reply I will enqueue the message and letting the form dequeuiong them, so the order will be preserved. Some concurrent strategy has to be provided to let the threads feed the queue while the form is dequeuing them, but you ensure the order of events is preserved, and you can start the form at the first event seen.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜