我有一个后台服务,可以在长时间运行的线程上执行一些任务和作业。为简单起见,我们将其视为进行异步 SQL 调用并重复的调度程序。现在,我使用 AsyncLocal 存储元数据以用于服务上的日志记录,并且我希望将任务的每次重复分组为一个事务。我认为与 Web 应用程序不同,BackgroundService 将有一个 AsyncLocal(ExecutionContext) 对象,我必须在每次重复后清除该对象(本例中的每个 SQL 调用)。说到问题,我似乎无法清理该对象。
这是我的 AsyncLocal 存储类的实现。
public class AsyncLocalStorage<T> : ILocalStorage<T>
{
private readonly AsyncLocal<T> _context;
public AsyncLocalStorage()
{
_context = new AsyncLocal<T>(OnValueChanged);
}
private static void OnValueChanged(AsyncLocalValueChangedArgs<T> args)
{
Log("OnValueChanged! Prev: {0} ; Current: {1}", RuntimeHelpers.GetHashCode(args.PreviousValue), RuntimeHelpers.GetHashCode(args.CurrentValue));
}
public T GetData()
{
try
{
return _context.Value;
}
catch (Exception ex)
{
Log("Ex: " + ex.ToString());
}
return default(T);
}
public void SetData(T value)
{
_context.Value = value;
}
public void Clear()
{
_context.Value = default(T);
}
}
在这里,我将元数据设置为 AsyncLocal 对象,然后调用 Clear 函数。但对象实例仍然存在。我已附上日志以供进一步参考。
02-05-2024 20:10:38 - [4:(DEBUG)<t:4>] - [TName: ]TRANSACTION STARTED (The AsyncLocal Object is created and I assign my object "Transaction" using context.SetData())
02-05-2024 20:10:38 - [1:(ERROR)<t:4>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [4:(DEBUG)<t:4>] - [TName: ]The async SQL call is made.... (The Transaction object is modified context.GetData() . I guess the obj is passed to thread 9 )
02-05-2024 20:10:39 - [1:(ERROR)<t:9>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [4:(DEBUG)<t:4>] - [TName: ]---- random things are taken care off using the Transaction object that resulted in the following OnValueChanged logs .
02-05-2024 20:10:39 - [1:(ERROR)<t:5>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [1:(ERROR)<t:9>] - OnValueChanged! Prev: 5773521 ; Current: 0
02-05-2024 20:10:39 - [1:(ERROR)<t:5>] - OnValueChanged! Prev: 5773521 ; Current: 0
02-05-2024 20:10:39 - [1:(ERROR)<t:5>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [1:(ERROR)<t:5>] - OnValueChanged! Prev: 5773521 ; Current: 0
02-05-2024 20:10:39 - [4:(DEBUG)<t:4>] - [TName: ]The Async SQL call returned a Task object .
02-05-2024 20:10:39 - [1:(ERROR)<t:9>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [4:(DEBUG)<t:4>] - [TName: ]Processing of the metadata done on thread 4 is over.
02-05-2024 20:10:39 - [1:(ERROR)<t:4>] - OnValueChanged! Prev: 5773521 ; Current: 0
02-05-2024 20:10:39 - [4:(DEBUG)<t:9>] - [TName: ]Most of the processing on Thread 9 is also over and The Task returned by SQL call has finished.
02-05-2024 20:10:39 - [4:(DEBUG)<t:9>] - [TName: ]Now the Transaction object is fetched with context.GetData() and is Loaded into a Thread Processor that runs independently using "ThreadPool.QueueWorkItem(Event, transaction) , which happens to be thread 10"
02-05-2024 20:10:39 - [1:(ERROR)<t:10>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:39 - [4:(DEBUG)<t:9>] - [TName: ]Everything seems to be over and its time to call context.Clear() //called.
02-05-2024 20:10:39 - [1:(ERROR)<t:9>] - OnValueChanged! Prev: 5773521 ; Current: 0
02-05-2024 20:10:39 - [4:(DEBUG)<t:9>] - [TName: ]Tried getting the transaction object here to verify using context.GetData(), returned null here.
02-05-2024 20:10:39 - [4:(DEBUG)<t:10>] - [TName: ProcessThread]Working with the transaction object reference in the Event thread.
02-05-2024 20:10:39 - [4:(DEBUG)<t:10>] - [TName: ProcessThread]Also working with the transaction object that is passed to this thread processor. Have no idea why the below log has occured.
02-05-2024 20:10:39 - [1:(ERROR)<t:10>] - OnValueChanged! Prev: 5773521 ; Current: 0 (I am sure the transaction object goes out of scope here)
02-05-2024 20:10:39 - [4:(DEBUG)<t:4>] - [TName: ]TRANSACTION STARTED (new repetition, calling the GetData function again before creating a new transaction object and assigning. I only assign a new object if it is null)
02-05-2024 20:10:40 - [1:(ERROR)<t:4>] - OnValueChanged! Prev: 0 ; Current: 5773521
02-05-2024 20:10:40 - [4:(DEBUG)<t:4>] - [TName: ] Suprise suprise!! it is not NULL. I have no idea why it isn't .
02-05-2024 20:10:40 - [4:(DEBUG)<t:4>] - [TName: ]I have already cleared all the component objects in the transaction object . A New transaction object is needed here.
为什么会发生这种情况,是我遗漏了什么吗?
编辑:最小的可复制示例如下。我试过把它变小
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly BackgroundTask _backgroundtask;
public Worker(ILogger<Worker> logger, BackgroundTask task)
{
_logger = logger;
_backgroundtask = task;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
_backgroundtask.MakeTask();
await Task.Delay(1000, stoppingToken);
}
}
}
public sealed class BackgroundTask
{
private readonly ILogger<BackgroundTask> _logger;
public BackgroundTask(ILogger<BackgroundTask> logger)
{
_logger = logger;
}
public void MakeTask()
{
Transactions trans = TransactionService.GetCurrentTransaction();
if(trans == null)
{
Console.WriteLine("[T:<{0}>] There is no TransactionObject", Thread.CurrentThread.ManagedThreadId);
trans = TransactionService.GetOrCreateTransaction();
}
else
{
Console.WriteLine("[T:<{0}>] There is a Transaction Object", Thread.CurrentThread.ManagedThreadId);
}
trans.PrintTransactionName();
Task.Run(() =>
{
trans.SetTransactionName("transaction2");
trans.PrintTransactionName();
trans.ClearEverything();
});
}
}
public class TransactionService
{
private static AsyncLocalStorage<Transactions> transactionContext;
static TransactionService()
{
transactionContext = new AsyncLocalStorage<Transactions>();
}
public static Transactions GetCurrentTransaction()
{
return transactionContext.GetData();
}
public static Transactions GetOrCreateTransaction()
{
var transaction = GetCurrentTransaction();
if (transaction == null)
{
transaction = new Transactions("Transaction1");
transactionContext.SetData(transaction);
}
return transaction;
}
public static void RemoveOutstandingTransactions()
{
transactionContext.Clear();
}
}
public class Transactions
{
private string transactionName;
public Transactions(string trans)
{
transactionName = trans;
}
public void SetTransactionName(string trans)
{
transactionName = trans;
}
public void PrintTransactionName()
{
if (!string.IsNullOrEmpty(transactionName))
{
Console.WriteLine("[T:<{0}>] The transactionName is {1}", Thread.CurrentThread.ManagedThreadId, transactionName);
}
}
public void ClearEverything()
{
transactionName = null;
Console.WriteLine("[T:<{0}>] The Transaction Object is being cleared", Thread.CurrentThread.ManagedThreadId);
TransactionService.RemoveOutstandingTransactions();
// Just checking if the object is cleared in this thread.
if(TransactionService.GetCurrentTransaction() == null)
{
Console.WriteLine("[T:<{0}>] The Transaction Object is null after clearing", Thread.CurrentThread.ManagedThreadId);
}
}
}
像上面的例子一样使用.NET core制作一个Worker-Service。 为什么清除 AsyncLocal 对象后,在下一次重复开始时会有一个 Transaction 对象。日志将类似于...
[T:<1>] There is no TransactionObject
[T:<1>] OnValueChanged! Prev: 0 ; Current: 72766
[T:<1>] The transactionName is Transaction1
[T:<9>] OnValueChanged! Prev: 0 ; Current: 72766
[T:<9>] The transactionName is transaction2
[T:<1>] OnValueChanged! Prev: 72766 ; Current: 0
[T:<9>] The Transaction Object is being cleared
[T:<9>] OnValueChanged! Prev: 72766 ; Current: 0
[T:<9>] The Transaction Object is null after clearing
[T:<6>] There is a Transaction Object
[T:<6>] OnValueChanged! Prev: 72766 ; Current: 0
[T:<9>] OnValueChanged! Prev: 0 ; Current: 72766
[T:<9>] The transactionName is transaction2
[T:<9>] The Transaction Object is being cleared
[T:<9>] OnValueChanged! Prev: 72766 ; Current: 0
[T:<9>] The Transaction Object is null after clearing
[T:<9>] OnValueChanged! Prev: 0 ; Current: 72766
还有为什么对象哈希是相同的?
为什么会出现这种情况?我有什么遗漏的吗?
是的,
AsyncLocal
充当一种“异步流”的写时复制堆栈。在你的例子中,MakeTask
将第一个值放入AsyncLocal
中,所以基本上是asyncflow#1。当您运行 Task.Run()
时,它会创建一个新的 asyncflow#2。在您的情况下,清除发生在 asyncflow#2 中,由于写时复制行为,这对 asyncflow#1 没有影响,但您的工作线程仍在 asyncflow#1 中,其中您的对象是第一个创建的事务。因此,您总是会看到相同的哈希值。为了实现你想要的行为,你可以使方法 MakeTask
异步,即使内部没有等待,但在 while 循环中等待它;它将强制创建一个新的“异步流”,输出将如下所示:
[T:<7>] There is no TransactionObject
OnValueChanged! Prev: 0 ; Current: 11429296
[T:<7>] The transactionName is Transaction1
[T:<7>] The transactionName is transaction2
[T:<7>] The Transaction Object is being cleared
OnValueChanged! Prev: 11429296 ; Current: 0
[T:<7>] The Transaction Object is null after clearing
[T:<7>] There is no TransactionObject
OnValueChanged! Prev: 0 ; Current: 41622463
[T:<7>] The transactionName is Transaction1
[T:<7>] The transactionName is transaction2
[T:<7>] The Transaction Object is being cleared
OnValueChanged! Prev: 41622463 ; Current: 0
[T:<7>] The Transaction Object is null after clearing
info: WorkerService1.Worker[0]
Worker running at: 05/15/2024 22:59:18 +03:00
[T:<7>] There is no TransactionObject
info: WorkerService1.Worker[0]
Worker running at: 05/15/2024 22:59:19 +03:00
OnValueChanged! Prev: 0 ; Current: 31364015
[T:<7>] The transactionName is Transaction1
[T:<7>] The transactionName is transaction2
[T:<7>] The Transaction Object is being cleared
OnValueChanged! Prev: 31364015 ; Current: 0
[T:<7>] The Transaction Object is null after clearing