Search code examples
c#tasksystem.diagnosticsopen-telemetrysystem.threading.channels

How to not propagate Activity in Task.Factory.StartNew


I have the following code:

Task task = null;
var channel = System.Threading.Channels.Channel.CreateUnbounded<string>();

using (var activity = MyActivitySource.StartActivity("Parent"))
{
    task = Task.Factory.StartNew(async () =>
    {
        //Activity.Current = null;
        var item = await channel.Reader.ReadAsync();
        Console.WriteLine("Task: {0}", Activity.Current?.DisplayName);
    });
}

Console.WriteLine("Current end: {0}", Activity.Current?.DisplayName ?? "(null)");

await channel.Writer.WriteAsync("something");
await task;

I would like to start the task without injecting Activity. I cannot create the task outside the using(var acrivity...).

One option (I suppose) is setting Activity.Current = null at the beginning of the task. Is there an alternative option?


Solution

  • One option (I suppose) is setting Activity.Current = null at the beginning of the task. Is there an alternative option?

    My preferred solution would be to move the "processing" code into a separate top-level loop. I.e., move the code currently within StartNew into a completely separate method, wrap it in an await foreach (var item in channel.Reader.ReadAllAsync()), and start that loop at the time you start your channel. Thus essentially making it an ActionBlock of sorts.

    You may need to augment your current string item with other values to make that work. I.e., if you need per-item completion, then you can replace string with (string Item, TaskCompletionSource Completion).

    There is another solution that may work (but I really recommend using the top-level loop instead): you can suppress the logical context flow (which suppresses all AsyncLocal-style values). But then I would recommend ensuring the task has started on the thread pool thread before letting it escape that block, just in case some code waits synchronously. So that would look like this:

    using (var activity = MyActivitySource.StartActivity("Parent"))
    {
      using var suppressFlow = ExecutionContext.SuppressFlow();
      var taskStarted = new TaskCompletionSource(TaskCreationOptions.ExecuteContinuationsAsynchronously);
      task = Task.Run(async () =>
      {
        taskStarted.TrySetResult();
        var item = await channel.Reader.ReadAsync();
        Console.WriteLine("Task: {0}", Activity.Current?.DisplayName);
      });
    }
    

    But really, I think putting in a "main loop" would be better.