首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >线程安全日志记录窗体

线程安全日志记录窗体
EN

Stack Overflow用户
提问于 2011-06-23 20:06:28
回答 2查看 604关注 0票数 1

我有表单负责记录所有必要的信息从所有线程到它的richTextBox。当我想要从不同的线程访问它的控件(将文本附加到richTextBox)时,我使用Invoke,并且它工作得很好。

但是当第一条消息要被追加到richTextBox时,我需要显示这个日志表单,而我不知道我的线程中的哪个将首先执行此操作。此外,当我关闭日志窗体时,我希望它在下一条消息到来时再次显示(在这种情况下,我仍然不知道哪个线程将首先调用它)。

我试图在新的线程中和通过Application.Run(ApplicationContext)创建这个表单,但是这些解决方案都不起作用。

你有什么提示吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-06-23 20:15:43

this other reply类似,我会将消息入队,并让表单将其出队,因此顺序将被保留。必须提供一些并发策略,以便在表单将线程出队时让它们为队列提供数据,但您要确保事件的顺序得到保留,并且可以在看到的第一个事件时启动表单。

票数 1
EN

Stack Overflow用户

发布于 2011-06-23 20:10:34

与关闭/重新生成窗口相比,简单地隐藏和取消隐藏通常更容易。

每次创建主窗口时都要创建该窗口,并且永远不要关闭它。添加一个onbeforeclose (或其他任何名称...)事件处理程序,该事件处理程序取消用户启动的关闭操作,而隐藏窗口。

现在,您已经在使用线程安全的调度程序来修改窗口内容:只需向该事件处理程序添加一行代码即可取消隐藏窗口(如果窗口已经可见,则不会执行任何操作),现在就可以开始了!

顺便说一句,在这些场景中有一个很有用的东西,那就是定制的TextWriter子类。您可以按如下方式创建一个:

代码语言:javascript
复制
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); 
    }
}

这样你就可以把你的表单更新逻辑放到下面这样的东西里了。

代码语言:javascript
复制
var threadSafeLogWriter =  new DelegateTextWriter(str => {
        Action updateCmd = ()=>{
            myControl...//append text to whatever control here
            myControl.Show();
        };
        if(myControl.InvokeRequired) myControl.BeginInvoke(updateCmd);
        else updateCmd();
    });

..。在任何地方都可以使用它,甚至有可能执行Console.SetOut来捕获Console.Write的输出。在多线程的这个TextWriter上调用Write是可以的,因为它的实现是自同步的。

你可以像这样测试它...

代码语言:javascript
复制
Console.SetOut(threadSafeLogWriter);
Parallel.For(0,100, i=>{
    threadSafeLogWriter.Write("Hello({0}) ", i);
    Thread.Yield();
    Console.WriteLine("World({0})!",i);
});

如果您对小消息进行了大量日志记录,则可能会以大量BeginInvoke调用而告终,而且这些调用速度很慢。作为一种优化,您可以将所有日志消息排队到ConcurrentQueue或其他同步结构中,并且只有在队列入队之前队列为空时才使用BeginInvoke。这样,您通常只在两次UI更新之间执行一次BeginInvoke;当UI开始执行它的操作时,它会清除该标志,然后一次追加所有排队的文本。然而,由于所有执行日志记录的线程只是按照它们进入记录器的顺序来转储它们的消息,因此拥有许多小的日志语句对于可读性来说是非常糟糕的;最好一次记录尽可能大的字符串以确保它不会被另一个线程消息中断;如果这样做,那么您就不会有太多的BeginInvoke的方式和性能。将不再是什么问题。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6453711

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档