与关闭/重新生成窗口相比,简单地隐藏和取消隐藏通常更容易。
在您创建主窗口时创建窗口并且从不关闭它。添加一个 onbeforeclose(或任何再次调用的...)事件处理程序,取消用户启动的关闭并隐藏窗口。
现在,您已经在使用线程安全的调度程序来修改窗口内容:只需在该事件处理程序中添加一行即可取消隐藏窗口(如果窗口已经可见,则不会执行任何操作),您就可以很高兴!
顺便说一句,在这些场景中一个有用的东西是自定义 TextWriter 子类。您可以按如下方式制作:
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);
}
}
这样你就可以将你的表单更新逻辑粘贴到这样的东西中......
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 的输出。由于实现是自同步的,因此可以在多个线程上调用 this TextWriter 上的 Write。
你可以这样测试...
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;当用户界面开始做这件事时,它会清除标志,然后一次附加所有排队的文本。然而,由于所有进行日志记录的线程只是按照它们进入记录器的任何顺序转储它们的消息,因此具有大量小日志语句的可读性可能非常糟糕;最好一次记录尽可能大的字符串,以确保它不会被另一个线程消息打断;如果你这样做,那么无论如何你将不会有很多 BeginInvoke 和性能。将不再是一个问题。