我正在使用 C# 工作,我注意到我的线程没有按照我期望的方式工作。首先,我创建了一个带有自己线程的 BlockingCollection 来打印控制台消息,因为我知道打印会显着降低性能并扰乱结果。
public static readonly BlockingCollection<string> DebugMessages = new BlockingCollection<string>();
public Form1()
{
Thread debugThread = new Thread(() =>
{
foreach(string msg in DebugMessages.GetConsumingEnumerable())
{
Console.WriteLine(msg);
}
});
debugThread.IsBackground = true;
debugThread.Priority = ThreadPriority.BelowNormal;
debugThread.Start();
然后,我还通过执行
new Thread
创建了另一个线程,该线程正在对不同的 BlockkingCollection 进行更多工作,该集合应该不断有要处理的对象。解析每个对象后,我运行以下代码来查看当前时间。我们可以将此线程称为线程 B。
long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds();
double diff = currentTime - time;
Form1.DebugMessages.Add($"TimeDiff:{diff} Message received: "); // + e.Message
time = currentTime;
然后在这个线程内部,我有时会运行以下代码,其想法是从线程 B 的角度出发,然后忘记。我将这个工作线程称为线程 C。这里的想法是针对线程 C 中使用
await
的所有内容
运算符阻止线程 C,但从不阻止线程 B。
Task.Run(async () =>
{
await BuyTokenAsync();
});
每次我调用在
await
内部使用 BuyTokenAsync
的方法时,我的 TimeDiff 标记始终处于关闭状态,这样看起来它会阻塞线程 B。这是从单独线程打印的一些调试消息的示例。
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
ATA created transaction sent:
TimeDiff:58 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:1 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:1 Message received:
TimeDiff:0 Message received:
Buy transaction sent:
TimeDiff:317 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:0 Message received:
TimeDiff:1 Message received:
现在位于
ATA created transaction sent
内的 BuyTokenAsync
对应的代码如下。
Form1.DebugMessages.Add($"ATA created transaction sent:");
var ataSendResponse = await rpcClient.SendTransactionAsync(ataTransaction.Serialize(), skipPreflight: true);
if (!ataSendResponse.WasSuccessful)
{
Form1.DebugMessages.Add($"Failed to send ATA creation transaction Reason:{ataSendResponse.Reason} Result:{ataSendResponse.Result} Error:{ataSendResponse.ServerErrorCode}");
throw new Exception($"Failed to send ATA creation transaction Reason:{ataSendResponse.Reason}");
}
其次,与
Buy transaction sent:
调试消息对应的代码如下,它也位于BuyTokenAsync
内,并且也应该在线程C上运行。
Form1.DebugMessages.Add($"Buy transaction sent:");
// Confirm the buy transaction
var confirmResponse = await rpcClient.GetTransactionAsync(sendResponse.Result, Commitment.Confirmed);
if (!confirmResponse.WasSuccessful)
{
Form1.DebugMessages.Add($"Failed to confirm buy transaction. Reason{confirmResponse.Reason} Result:{confirmResponse.Result} Error:{confirmResponse.ServerErrorCode}");
throw new Exception($"Failed to confirm buy transaction. Reason{confirmResponse.Reason} Result:{confirmResponse.Result} Error:{confirmResponse.ServerErrorCode}");
}
基于
public async Task<string> BuyTokenAsync()
方法每次都会发生这种情况,我知道它必须以某种方式阻塞线程 B 或者正在打印的 BlockingCollection 线程?我不确定为什么或如何这是可能的。另请注意,我的代码中没有使用锁或类似的东西。我什至尝试将Task.Run
更改为以下内容,但同样的问题仍然存在,我做错了什么?
Task.Factory.StartNew(async () =>
{ // Fully isoloate the following thread from its parent thread above
try
{
Form1.DebugMessages.Add("Buying");
await BuyTokenAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Form1.DebugMessages.Add($"Error in BuyTokenAsync: {ex.Message}");
}
}, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);**
首先,不存在“线程C”。
Task.Run
将在任意线程池线程上运行代码。因此,原则上任意数量的 BuyTokenAsync
调用都可以并发运行,并且可以按任意顺序运行。
每次我调用在 BuyTokenAsync 内部使用 wait 的方法时,我的 TimeDiff 标记始终处于关闭状态,因此它似乎阻塞了线程 B
使用 Stopwatch 来测量时间,DateTime 通常具有 16ms 的分辨率,对于这样的代码来说这是一个永恒。您还可以考虑使用性能分析器来更好地可视化每个线程实际执行的操作。
但是从例子中并不清楚实际的问题是什么。
Task.Run
不应阻塞调用线程,但如果没有足够数量的内核来同时运行所有线程,则可能会导致线程 B 被抢占。或者例子中有些东西没有显示出来。
防止工作线程在父线程上运行
这实际上没有任何意义,一个线程不能运行另一个线程。线程在 CPU 上运行,task 在线程上运行。但任务不会在任意线程上运行,它们要么在空闲线程池线程上运行,要么在 UI 线程上运行,或者在自定义任务调度程序提供的线程上运行。
Form1.DebugMessages.Add($"TimeDiff:{diff} 收到消息:")
这有点可疑,因为它看起来像是在更新 UI,而这是非 UI 线程的一大禁忌。如果您没有收到任何错误,我猜您正在使用
.Invoke(...)
或 .BeginInvoke(...)
将执行移至 UI 线程?请注意,.Invoke
将阻塞调用线程,直到 UI 更新为止,并且这可能会序列化所有方法调用。
总的来说,我建议使用像 NLog 这样的库来进行日志记录,如果您指定异步日志记录,它会为您处理所有排队的事情,以及许多其他问题。如果您尝试管道化您的工作,数据流或通道是您可能需要考虑的其他库。我还建议使用性能分析器来测量性能,并且只对实际需要它的东西进行后台处理。