C# 线程安全日志记录无法在调试器中使用任务或线程
我尝试将C# 线程安全日志记录无法在调试器中使用任务或线程,c#,multithreading,task,invoke,C#,Multithreading,Task,Invoke,我尝试将任务或线程中的文本消息记录到表单上的文本框中。为此,我使用Invoke和invokererequired方法与主线程同步,这在internet上的许多示例中都可以找到。请参见下面的LogMessage\u委托和LogMessage\u Threadsafe。当我关闭应用程序时,布尔标志finished被设置为true,任务/线程应该停止工作 在我在Form1\u FormClosing事件处理程序(finished=true;)的第一行设置断点之前,这一切都可以正常工作。然后我只看到控制
任务
或线程
中的文本消息记录到表单上的文本框中。为此,我使用Invoke
和invokererequired
方法与主线程同步,这在internet上的许多示例中都可以找到。请参见下面的LogMessage\u委托
和LogMessage\u Threadsafe
。当我关闭应用程序时,布尔标志finished
被设置为true,任务/线程应该停止工作
在我在Form1\u FormClosing
事件处理程序(finished=true;
)的第一行设置断点之前,这一切都可以正常工作。然后我只看到控制台消息“logmessageinvokererequired”,但没有相应的“LogMessage”,应用程序挂起
如果我注释掉LogMessage\u Threadsafe
调用Work
(仅控制台消息),则它会再次工作。应用程序正在关闭,如预期的那样
那么,有人能向我解释一下这种行为吗?我找不到理由
请注意,我在Form1\u FormClosing
事件处理程序中进行了标记,因此表单仍然有效
namespace MultiThreadedTest
{
public partial class Form1 : Form
{
//************************************************************
// Fields
Thread worker = null;
Task task = null;
bool finished = false;
//************************************************************
// Constructor
public Form1()
{
InitializeComponent();
worker = new Thread(Work);
worker.Start();
//task = Task.Factory.StartNew(Work);
}
//************************************************************
// Helper methods
public void LogMessage(string sMessage)
{
LogTextBox.Text += sMessage + Environment.NewLine;
}
/// <summary>
/// Threadsafe wrapper for LogMessage
/// </summary>
delegate void LogMessage_Delegate(string sMessage);
public void LogMessage_Threadsafe(string sMessage)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.InvokeRequired)
{
Console.WriteLine("LogMessage InvokeRequired");
LogMessage_Delegate callback = new LogMessage_Delegate(LogMessage_Threadsafe);
this.Invoke(callback, new object[] { sMessage });
}
else
{
Console.WriteLine("LogMessage");
LogMessage(sMessage);
}
}
//************************************************************
// Commands
void Work()
{
while (!finished)
{
Console.WriteLine("Tread/Task Waiting...");
LogMessage_Threadsafe("Tread/Task Waiting...");
Thread.Sleep(1000); // Wait a little...
}
Console.WriteLine("Thread/Task Done");
}
//************************************************************
// Events
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
finished = true;
if (worker != null) worker.Join();
if (task != null) Task.WaitAll(task);
Console.WriteLine("App Done");
}
}
}
名称空间多线程测试
{
公共部分类Form1:Form
{
//************************************************************
//田地
线程工作者=null;
Task=null;
bool finished=false;
//************************************************************
//建造师
公共表格1()
{
初始化组件();
工人=新线程(工作);
worker.Start();
//task=task.Factory.StartNew(工作);
}
//************************************************************
//辅助方法
公共无效日志消息(字符串消息)
{
LogTextBox.Text+=sMessage+Environment.NewLine;
}
///
///日志消息的线程安全包装器
///
delegate void LogMessage_delegate(字符串消息);
public void LogMessage_Threadsafe(字符串消息)
{
//invokererequired比较
//向创建线程的线程ID调用线程。
//如果这些线程不同,则返回true。
if(this.invokererequired)
{
Console.WriteLine(“需要调用日志消息”);
LogMessage\u Delegate callback=新的LogMessage\u Delegate(LogMessage\u Threadsafe);
调用(回调,新对象[]{sMessage});
}
其他的
{
Console.WriteLine(“日志消息”);
日志信息(sMessage);
}
}
//************************************************************
//命令
无效工作()
{
当(!完成)
{
Console.WriteLine(“踏板/任务等待…”);
LogMessage_Threadsafe(“踏板/任务等待…”);
Thread.Sleep(1000);//稍等。。。
}
Console.WriteLine(“线程/任务完成”);
}
//************************************************************
//事件
私有作废Form1\u FormClosing(对象发送方,FormClosingEventArgs e)
{
完成=正确;
如果(worker!=null)worker.Join();
if(task!=null)task.WaitAll(task);
Console.WriteLine(“应用程序完成”);
}
}
}
如果在UI线程中的断点处暂停,则通过Invoke
封送到UI线程的调用将不会执行,因为它们在暂停的UI线程上运行
但从你的评论来看,这似乎不是问题所在。所以我猜问题是,通过在断点处暂停,您已经允许后台线程进入一种状态,即它在Invoke()
上阻塞,然后您尝试加入该后台线程,该线程将阻塞,直到Invoke
完成,这是永远不会发生的
作为一个单独的问题,如果从多个线程访问
finished
,则需要使用lock
块包围读写,以确保线程安全。如果在UI线程的断点处暂停,则通过Invoke
封送到UI线程的调用将不会执行,因为它们在UI线程上运行,暂停
但从你的评论来看,这似乎不是问题所在。所以我猜问题是,通过在断点处暂停,您已经允许后台线程进入一种状态,即它在Invoke()
上阻塞,然后您尝试加入该后台线程,该线程将阻塞,直到Invoke
完成,这是永远不会发生的
作为一个单独的问题,如果要从多个线程访问
finished
,则需要使用lock
块来包围读写,以确保线程安全。自从.NET 4引入任务以来,您不需要使用原始线程。Invoke也不需要,但自从.NET4.5引入了async/await
之后,它就过时了。4.5还引入了线程安全进度报告和与的取消,如中所述
Progress
在创建它的线程上调用它的委托,在本例中是UI线程。您可以将接口传递给任何后台方法(任务、线程方法等),并使用它报告进度
考虑到最早支持的.NET版本是4.5.2,您可以假定这些类始终可用。顺便说一句,TLS1.2支持是在4.5.2中添加的,因此任何抵制者都必须升级,因为他们发现自己无法连接到GMail或其他需要TLS1.2的服务
使用这些类可以大大简化代码。带b的快速脏表单
public partial class Form1 : Form
{
System.Threading.Timer _timer;
IProgress<string> _progress;
public Form1()
{
InitializeComponent();
_progress = new Progress<string>(msg => textBox1.Text += msg + "\r\n");
_timer = new System.Threading.Timer(theCallback);
}
private async void theCallback(object state)
{
for (int i = 0; i < 5; i++)
{
await Task.Delay(100);
_progress.Report($"Boo {i}");
}
}
private void Form1_Load(object sender, EventArgs e)
{
_timer.Change(0, 10000);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_timer.Dispose();
_timer = null;
_progress = null;
}
}