Reputation: 377
So I currently have an application (simplified) that takes an ID and makes Web Requests and I am trying to implement a logger for this. The current approach was to create a simple static LoggingHelper class which I can call LoggingHelper.Log(url) from anywhere in my program to log the URL to a list and in my final report for the supplied ID, include the list at the end. My Report object calls LoggingHelper.Initialize() in its constructor to create a new empty list.
public static class LoggingHelper
{
static List<string> _urlLog;
public static void Initialize()
{
_urlLog = new List<string>();
}
public static void Log(String s)
{
_urlLog.Add(s);
}
public static List<String> GetLogs()
{
return _urlLog;
}
}
This works perfectly on my local machine when testing since I am only able to run a single ID per run. However when this is deployed, the process can run up to 8 IDs in parallel all using the same application, which means my static list is now shared by all 8 runs that are happening at the exact same time since static variables are always shared by all the threads in the process. Once deployed, this causes logs from the wrong run being written to the wrong report since different threads within the process are all running at the same time and writing to the same static instance.
Normally I would just initialize the list as an member variable for my report and log it directly, but in the places that I need to call the Log function, I have no access to the Report object which is why I used a static variable and accessor.
I was wondering if there was an equivalent of thread_local from C++ in C# (and if that would even work for my case) or if anyone had any other suggestions on going about implementing this. Passing a reference to my report object (which would could hold an member variable of List) is something I would like to avoid.
I thought of using a Singleton but I will still have the same issue since the instance variable is static it would still hold the same shared reference to the list.
Any ideas on how I would implement a list that is unique to each run while still allowing the global access that the static keyword provides?
Please let me know if any clarification or additional details are needed, dealing with multiple threads in this specific case is rather new to me. Thanks!
Upvotes: 1
Views: 159
Reputation: 33833
As mentioned in the above comment, the ThreadLocal<T>
class is designed to handle this type of requirement. That said, it sounds like you need to persist across async boundaries, which requires the AsyncLocal<T>
class instead.
public static class LoggingHelper
{
private static AsyncLocal<List<string>> t_urlLog = new AsyncLocal<List<string>>();
public static void Log(String s)
{
if (t_urlLog.Value is null)
{
t_urlLog.Value = new List<string>();
}
t_urlLog.Value.Add(s);
}
public static List<String> GetLogs()
{
return t_urlLog.Value;
}
}
Note that this eliminates the need for your Initialize()
method.
You can test this with the following:
void Main()
{
async Task DoWorkAsync()
{
LoggingHelper.Log($"first add to log by thread: {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1);
LoggingHelper.Log($"second add to log by thread: {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1);
LoggingHelper.GetLogs().Dump();
}
var addTasks = new List<Task>();
for (int i = 0; i < 8; i++)
{
addTasks.Add(Task.Run(DoWorkAsync));
}
Task.WhenAll(addTasks).GetAwaiter().GetResult();
}
which outputs the following:
Note that in this example, different threads are writing the various log calls across multiple async boundaries, but each async-bound list only contains two items, as expected.
Upvotes: 1