如何清理/清除BackgroundService(Microsoft.Hosting.Extensions.BackgroundService)中的AsyncLocal<T>对象?

问题描述 投票:0回答:1

我有一个后台服务,可以在长时间运行的线程上执行一些任务和作业。为简单起见,我们将其视为进行异步 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

还有为什么对象哈希是相同的?

c# multithreading asynchronous background-service executioncontext
1个回答
0
投票

为什么会出现这种情况?我有什么遗漏的吗?

是的,

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
© www.soinside.com 2019 - 2024. All rights reserved.