Concurrent dictionary Lazy<T> delegate is being invoked multiple times - c#

I am trying to use ConcurrentDictionary<TKey,Lazy<T>> to get a token and cache it in-memory. The GetToken() method contains logic to read existing tokens from database, check for token expiry and make an HTTP call to refresh the token if the existing token has expired.
public static class TokenCache
{
private static ConcurrentDictionary<int, Lazy<Token>> _tokens = new
ConcurrentDictionary<int, Lazy<Token>>();
private static int dbCount = 0;
public static Token GetToken(int id)
{
// Get the lazy token object
Lazy<Token> lazyToken= _tokens.GetOrAdd(id, key => new Lazy<Token>(() =>
{
var token=GetTokenFromDB(id);
Interlocked.Increment(ref dbCount);
Console.WriteLine("GetOrAdd " + dbCount);
if ((token.ExpiryDate - DateTime.UtcNow).TotalMinutes <= 5)
{
_tokens.TryRemove(id, out _);
return RefreshToken(id, cred);
}
return token;
}));
// Access the token
return lazyToken.Value;
}
}
Parallel.For(1, 100, _ => TokenCache.GetToken(555));
My understanding is if two threads simultaneously call GetToken() and then the GetOrAdd() method, it will create two Lazy<Token> objects but at this point no database call and HTTP call is made yet. The db calls (and token refresh HTTP request) are invoked when we call .Value and thus we will be having a single db call and token refresh even when there are multiple threads. However, when I tested this, I am seeing that the delegate inside Lazy<Token> is being invoked 5-7 times when about 100 threads are run parallelly.
Shouldn't the delegate for Lazy<T> be invoked only once?

Related

Concurrent Dictionary reading and removing items In parallel

Question background:
I am currently learning about concurrent operations using the.NET 6 Parallel.ForEachAsync loop.
I have created the following program which features two Tasks running parallel to each other in a Task.WhenAll function. The KeepAliveAsync method runs up to 3 degrees of parallelism on the HttpClient calls for a total list of ten items. The DisposeAsync method will remove these items from the concurrent dictionary but before this, the CleanItemBeforeDisposal method removes the property values of the Item object.
Code:
{
[TestClass]
public class DisposeTests
{
private ConcurrentDictionary<int, Item> items = new ConcurrentDictionary<int, Item>();
private bool keepAlive = true;
[TestMethod]
public async Task Test()
{
//Arrange
string uri = "https://website.com";
IEnumerable<int> itemsToAdd = Enumerable.Range(1, 10);
IEnumerable<int> itemsToDispose = Enumerable.Range(1, 10);
foreach (var itemToAdd in itemsToAdd)
{
items.TryAdd(itemToAdd, new Item { Uri = uri });
}
//Act
await Task.WhenAll(KeepAliveAsync(), DisposeAsync(itemsToDispose));
//Assert
Assert.IsTrue(items.Count == 0);
}
private async Task KeepAliveAsync()
{
HttpClient httpClient = new HttpClient();
do
{
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = 3,
};
await Parallel.ForEachAsync(items.ToArray(), parallelOptions, async (item, token) =>
{
var response = await httpClient.GetStringAsync(item.Value.Uri);
item.Value.DataResponse = response;
item.Value.DataResponse.ToUpper();
});
} while (keepAlive == true);
}
private async Task DisposeAsync(IEnumerable<int> itemsToRemove)
{
var itemsToDisposeFiltered = items.ToList().FindAll(a => itemsToRemove.Contains(a.Key));
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = 3,
};
await Parallel.ForEachAsync(itemsToDisposeFiltered.ToArray(), parallelOptions, async (itemsToDispose, token) =>
{
await Task.Delay(500);
CleanItemBeforeDisposal(itemsToDispose);
bool removed = items.TryRemove(itemsToDispose);
if (removed == true)
{
Debug.WriteLine($"DisposeAsync - Removed item {itemsToDispose.Key} from the list");
}
else
{
Debug.WriteLine($"DisposeAsync - Did not remove item {itemsToDispose.Key} from the list");
}
});
keepAlive = false;
}
private void CleanItemBeforeDisposal(KeyValuePair<int, Item> itemToDispose)
{
itemToDispose.Value.Uri = null;
itemToDispose.Value.DataResponse = null;
}
}
}
The Issue:
The code runs but I am running into an issue where the Uri property of the Item object is being set null from the CleanItemBeforeDisposal method as called from the Dispose method by design but then the HttpClient call is being made in the parallel KeepAliveAsync method at which point the shared Item object is null and errors with:
System.InvalidOperationException: An invalid request URI was provided. Either the request URI must be an absolute URI or BaseAddress must be set.
I have used the ToArray method on the shared ConcurrentDictionary as I believe this will create a snapshot of the dictionary at the time it is called but obviously this wont solve this race condition.
What is the correct way to go about handling a situation where two processes are accessing one shared list where there is the possibility one process has changed properties of an entity of that list which the other process requires?
I'll try to answer the question directly without getting into details about the design, etc.
ConcurrentDictionary is thread safe, meaning that multiple threads can safely add and remove items from the dictionary. That thread safety does not apply at all to whatever objects are stored as values in the dictionary.
If multiple threads have a reference to an instance of Item and are updating its properties, all sorts of unpredictable things can happen.
To directly answer the question:
What is the correct way to go about handling a situation where two processes are accessing one shared list where there is the possibility one process has changed properties of an entity of that list which the other process requires?
There is no correct way to handle that possibility. If you want the code to work in a predictable way you must eliminate that possibility.
It looks like you might have hoped that somehow the two operations will stay in sync. They won't. Even if they did, just once, it might never happen again. It's unpredictable.
If you actually need to set the Uri and Response to null, it's probably better to do that to each Item in the same thread right after you're done using those values. If you do those three things
Execute the request for a single Item
Do something with the values
Set them to null
...one after another in the same thread, it's impossible for them to happen out of order.
(But do you need to set them to null at all? It's not clear what that's for. If you just didn't do it, then there wouldn't be a problem to solve.)

Call WebMethod asynchronously using semaphore and timeout in C#

I am new to web based C# programming and asynchronous WebMethod calling. I have read couple of articles but does not find any effective way.
What can be done if I have 100's of web service method call and I want to call asynchronously 10 methods simultaneously among them. Wait for 5000 milliseconds for each WebMethod to response, provide timeout if response does not arrive in 5000 milliseconds and based on the semaphore releases from that 10 calls, I will call new methods from rest of the WebMethod.
what is the best way to implement this kind of scenario?
Sample:
I have shown 1 sample method only, but this way I have to call 100s of methods asynchronously.
SemaphoreSlim Semaphore = new SemaphoreSlim(0,10);
//Register method
soapClient.MethodNameCompleted += soapClient_MethodNameCompleted;
//Call method async
MethodNameAsync();
Semaphore.Wait()
//This method calls only when WebMethod send a response
void soapClient_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
string result = e.Result.ToString();
Semaphore.Release();
}
May be middleware help you. For example:
public class SemaphoreMiddleware
{
private readonly static SemaphoreSlim semaphore = new SemaphoreSlim(10, 10);
private readonly RequestDelegate nextMiddleware;
public SemaphoreMiddleware(RequestDelegate nextMiddleware)
{
this.nextMiddleware = nextMiddleware;
}
public async Task InvokeAsync(HttpContext httpContext)
{
await semaphoreSlim.WaitAsync(stoppingToken);
try
{
await nextMiddleware(httpContext);
}
finally
{
semaphore.Release();
}
}
}
ASP.NET Core Middleware https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.1

Azure KeyVault Active Directory AcquireTokenAsync timeout when called asynchronously

I have setup Azure Keyvault on my ASP.Net MVC web application by following the example in Microsoft's Hello Key Vault sample application.
Azure KeyVault (Active Directory) AuthenticationResult by default has a one hour expiry. So after one hour, you must get a new authentication token. KeyVault is working as expected for the first hour after getting my first AuthenticationResult token, but after the 1 hour expiry, it fails to get a new token.
Unfortunately it took a failure on my production environment for me to realize this, as I never tested past one hour in development.
Anyways, after over two days of trying to figure out what was wrong with my keyvault code, I came up with a solution that fixes all of my problems - remove the asynchronous code - but it feels very hacky. I want to find out why it was not working in the first place.
My code looks like this:
public AzureEncryptionProvider() //class constructor
{
_keyVaultClient = new KeyVaultClient(GetAccessToken);
_keyBundle = _keyVaultClient
.GetKeyAsync(_keyVaultUrl, _keyVaultEncryptionKeyName)
.GetAwaiter().GetResult();
}
private static readonly string _keyVaultAuthClientId =
ConfigurationManager.AppSettings["KeyVaultAuthClientId"];
private static readonly string _keyVaultAuthClientSecret =
ConfigurationManager.AppSettings["KeyVaultAuthClientSecret"];
private static readonly string _keyVaultEncryptionKeyName =
ConfigurationManager.AppSettings["KeyVaultEncryptionKeyName"];
private static readonly string _keyVaultUrl =
ConfigurationManager.AppSettings["KeyVaultUrl"];
private readonly KeyBundle _keyBundle;
private readonly KeyVaultClient _keyVaultClient;
private static async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(
_keyVaultAuthClientId,
_keyVaultAuthClientSecret);
var context = new AuthenticationContext(
authority,
TokenCache.DefaultShared);
var result = context.AcquireToken(resource, clientCredential);
return result.AccessToken;
}
The GetAccessToken method signature has to be asynchronous to pass into the new KeyVaultClient constructor, so I left the signature as async, but I removed the await keyword.
With the await keyword in there (the way it should be, and is in the sample):
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(_keyVaultAuthClientId, _keyVaultAuthClientSecret);
var context = new AuthenticationContext(authority, null);
var result = await context.AcquireTokenAsync(resource, clientCredential);
return result.AccessToken;
}
The program works fine the first time I run it. And for an hour, AcquireTokenAsync returns the same original authentication token which is great. But once the token expires, AcquiteTokenAsync should get a new token with a new expiry date. And it doesn't - the application just hangs. No error returned, nothing at all.
So calling AcquireToken instead of AcquireTokenAsync solves the problem, but I have no idea why. You'll also notice that I'm passing 'null' instead of 'TokenCache.DefaultShared' into the AuthenticationContext constructor in my sample code with async. This is to force the toke to expire immediately instead of after one hour. Otherwise, you have to wait an hour to reproduce the behavior.
I was able to reproduce this again in a brand new MVC project, so I don't think it has anything to do with my specific project. Any insight would be appreciated. But for now, I'm just not using async.
Problem: deadlock
Your EncryptionProvider() is calling GetAwaiter().GetResult(). This blocks the thread, and on subsequent token requests, causes a deadlock. The following code is the same as yours is but separates things to facilitate explanation.
public AzureEncryptionProvider() // runs in ThreadASP
{
var client = new KeyVaultClient(GetAccessToken);
var task = client.GetKeyAsync(KeyVaultUrl, KeyVaultEncryptionKeyName);
var awaiter = task.GetAwaiter();
// blocks ThreadASP until GetKeyAsync() completes
var keyBundle = awaiter.GetResult();
}
In both token requests, the execution starts in the same way:
AzureEncryptionProvider() runs in what we'll call ThreadASP.
AzureEncryptionProvider() calls GetKeyAsync().
Then things differ. The first token request is multi-threaded:
GetKeyAsync() returns a Task.
We call GetResult() blocking ThreadASP until GetKeyAsync() completes.
GetKeyAsync() calls GetAccessToken() on another thread.
GetAccessToken() and GetKeyAsync() complete, freeing ThreadASP.
Our web page returns to the user. Good.
The second token request uses a single thread:
GetKeyAsync() calls GetAccessToken() on ThreadASP (not on a separate thread.)
GetKeyAsync() returns a Task.
We call GetResult() blocking ThreadASP until GetKeyAsync() completes.
GetAccessToken() must wait until ThreadASP is free, ThreadASP must wait until GetKeyAsync() completes, GetKeyAsync() must wait until GetAccessToken() completes. Uh oh.
Deadlock.
Why? Who knows?!?
There must be some flow control within GetKeyAsync() that relies on the state of our access token cache. The flow control decides whether to run GetAccessToken() on its own thread and at what point to return the Task.
Solution: async all the way down
To avoid a deadlock, it is a best practice "to use async all the way down." This is especially true when we are calling an async method, such as GetKeyAsync(), that is from an external library. It is important not force the method to by synchronous with Wait(), Result, or GetResult(). Instead, use async and await because await pauses the method instead of blocking the whole thread.
Async controller action
public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
var provider = new EncryptionProvider();
await provider.GetKeyBundle();
var x = provider.MyKeyBundle;
return View();
}
}
Async public method
Since a constructor cannot be async (because async methods must return a Task), we can put the async stuff into a separate public method.
public class EncryptionProvider
{
//
// authentication properties omitted
public KeyBundle MyKeyBundle;
public EncryptionProvider() { }
public async Task GetKeyBundle()
{
var keyVaultClient = new KeyVaultClient(GetAccessToken);
var keyBundleTask = await keyVaultClient
.GetKeyAsync(KeyVaultUrl, KeyVaultEncryptionKeyName);
MyKeyBundle = keyBundleTask;
}
private async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
TokenCache.DefaultShared.Clear(); // reproduce issue
var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
var clientCredential = new ClientCredential(ClientIdWeb, ClientSecretWeb);
var result = await authContext.AcquireTokenAsync(resource, clientCredential);
var token = result.AccessToken;
return token;
}
}
Mystery solved. :) Here is a final reference that helped my understanding.
Console App
My original answer had this console app. It worked as an initial troubleshooting step. It did not reproduce the problem.
The console app loops every five minutes, repeatedly asking for a new access token. At each loop, it outputs the current time, the expiry time, and the name of the retrieved key.
On my machine, the console app ran for 1.5 hours and successfully retrieved a key after expiration of the original.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace ConsoleApp
{
class Program
{
private static async Task RunSample()
{
var keyVaultClient = new KeyVaultClient(GetAccessToken);
// create a key :)
var keyCreate = await keyVaultClient.CreateKeyAsync(
vault: _keyVaultUrl,
keyName: _keyVaultEncryptionKeyName,
keyType: _keyType,
keyAttributes: new KeyAttributes()
{
Enabled = true,
Expires = UnixEpoch.FromUnixTime(int.MaxValue),
NotBefore = UnixEpoch.FromUnixTime(0),
},
tags: new Dictionary<string, string> {
{ "purpose", "StackOverflow Demo" }
});
Console.WriteLine(string.Format(
"Created {0} ",
keyCreate.KeyIdentifier.Name));
// retrieve the key
var keyRetrieve = await keyVaultClient.GetKeyAsync(
_keyVaultUrl,
_keyVaultEncryptionKeyName);
Console.WriteLine(string.Format(
"Retrieved {0} ",
keyRetrieve.KeyIdentifier.Name));
}
private static async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(
_keyVaultAuthClientId,
_keyVaultAuthClientSecret);
var context = new AuthenticationContext(
authority,
TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, clientCredential);
_expiresOn = result.ExpiresOn.DateTime;
Console.WriteLine(DateTime.UtcNow.ToShortTimeString());
Console.WriteLine(_expiresOn.ToShortTimeString());
return result.AccessToken;
}
private static DateTime _expiresOn;
private static string
_keyVaultAuthClientId = "xxxxx-xxx-xxxxx-xxx-xxxxx",
_keyVaultAuthClientSecret = "xxxxx-xxx-xxxxx-xxx-xxxxx",
_keyVaultEncryptionKeyName = "MYENCRYPTIONKEY",
_keyVaultUrl = "https://xxxxx.vault.azure.net/",
_keyType = "RSA";
static void Main(string[] args)
{
var keepGoing = true;
while (keepGoing)
{
RunSample().GetAwaiter().GetResult();
// sleep for five minutes
System.Threading.Thread.Sleep(new TimeSpan(0, 5, 0));
if (DateTime.UtcNow > _expiresOn)
{
Console.WriteLine("---Expired---");
Console.ReadLine();
}
}
}
}
}
I have the same challenge you have. I am assuming that you've also seen the sample published at https://azure.microsoft.com/en-us/documentation/articles/key-vault-use-from-web-application/
There is a big difference between what that sample does and what my code does (and I think the intent of your code is). In the sample, they retrieve a secrete and store it in the web application as a static member of their Utils class. Thus, the sample retrieves a secret one time for the entire run time of the application.
In my case, I am retrieving a different key for different purposes at different times during the application's run time.
Additionally, the sample download you linked to uses an X.509 certificate to authenticate the web application to KeyVault, rather than client secret. It's possible there is an issue with that too.
I saw the chat with #shaun-luttin concluding you caused a deadlock, but that's not the whole story I think. I don't use .GetAwaiter().GetResult() or call an async method from a ctor.

Get Task CancellationToken

Can I get CancellationToken which was passed to Task constructor during task action executing. Most of samples look like this:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task myTask = Task.Factory.StartNew(() =>
{
for (...)
{
token.ThrowIfCancellationRequested();
// Body of for loop.
}
}, token);
But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?
But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?
Yes, in that case, you would need to pass the token boxed as state, or included in some other type you use as state.
This is only required if you plan to use the CancellationToken within the method, however. For example, if you need to call token.ThrowIfCancellationRequested().
If you're only using the token to prevent the method from being scheduled, then it's not required.
Can I get CancellationToken which was passed to Task constructor during task action executing?
No, you can't get it directly from the Task object, no.
But what if my action is not lambda but a method placed in other class and I don't have direct access to token? Is the only way is to pass token as state?
Those are two of the options, yes. There are others though. (Possibly not an inclusive list.)
You can close over the cancellation token in an anonymous method
You can pass it in as state
You can ensure that the instance used for the task's delegate has an instance field that holds onto the cancellation token, or holds onto some object which holds onto the token, etc.
You can expose the token though some other larger scope as state, i.e. as a public static field (bad practice in most cases, but it might occasionally be applicable)
This seems to work:
public static CancellationToken GetCancellationToken(this Task task)
{
return new TaskCanceledException(task).CancellationToken;
}
This can be necessary to make general-purpose Task helpers preserve the CancellationToken of a cancelled Task (I arrived here while trying to make Jon Skeet's WithAllExceptions method preserve the Token).
As other answers state, you can pass the token as a parameter to your method. However, it's important to remember that you still want to pass it to the Task as well. Task.Factory.StartNew( () => YourMethod(token), token), for example.
This insures that:
The Task will not run if cancellation occurs before the Task executes (this is a nice optimization)
An OperationCanceledException thrown by the called method correctly transitions the Task to a Canceled state
There is a very simple solution:
class CancelingTasks
{
private static void Foo(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
Thread.Sleep(100);
Console.Write(".");
}
}
static void Main(string[] args)
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken tok = source.Token;
tok.Register(() =>
{
Console.WriteLine("Cancelled.");
});
Task t = new Task(() =>
{
Foo(tok);
}, tok);
t.Start();
Console.ReadKey();
source.Cancel();
source.Dispose();
Console.WriteLine("Main program done, press any key.");
Console.ReadKey();
}
}
You can get the CancellationToken by accessing internal fields with reflection.
public CancellationToken GetCancellationToken(Task task)
{
object m_contingentProperties = task
.GetType()
.GetField("m_contingentProperties",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetValue(task);
object m_cancellationToken = m_contingentProperties
.GetType()
.GetField("m_cancellationToken",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
.GetValue(m_contingentProperties);
return (CancellationToken)m_cancellationToken;
}
Hint: You can search for such things on your own with ILSpy .
When we look at the Task class reference source code we can see that the cancellation token is stored inside an internal class: ContingentProperties
https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,90a9f91ddd80b5cc
The purpose is to avoid the access of these properties and those properties are not always necessary.
internal class ContingentProperties
{
// Additional context
internal ExecutionContext m_capturedContext; // The execution context to run the task within, if any.
// Completion fields (exceptions and event)
internal volatile ManualResetEventSlim m_completionEvent; // Lazily created if waiting is required.
internal volatile TaskExceptionHolder m_exceptionsHolder; // Tracks exceptions, if any have occurred
// Cancellation fields (token, registration, and internally requested)
internal CancellationToken m_cancellationToken; // Task's cancellation token, if it has one
internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task's registration with the cancellation token
internal volatile int m_internalCancellationRequested; // Its own field because threads legally ---- to set it.
// Parenting fields
// # of active children + 1 (for this task itself).
// Used for ensuring all children are done before this task can complete
// The extra count helps prevent the ---- for executing the final state transition
// (i.e. whether the last child or this task itself should call FinishStageTwo())
internal volatile int m_completionCountdown = 1;
// A list of child tasks that threw an exception (TCEs don't count),
// but haven't yet been waited on by the parent, lazily initialized.
internal volatile List<Task> m_exceptionalChildren;
/// <summary>
/// Sets the internal completion event.
/// </summary>
internal void SetCompleted()
{
var mres = m_completionEvent;
if (mres != null) mres.Set();
}
/// <summary>
/// Checks if we registered a CT callback during construction, and deregisters it.
/// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed
/// successfully or with an exception.
/// </summary>
internal void DeregisterCancellationCallback()
{
if (m_cancellationRegistration != null)
{
// Harden against ODEs thrown from disposing of the CTR.
// Since the task has already been put into a final state by the time this
// is called, all we can do here is suppress the exception.
try { m_cancellationRegistration.Value.Dispose(); }
catch (ObjectDisposedException) { }
m_cancellationRegistration = null;
}
}
}

How do I know when it's safe to call Dispose?

I have a search application that takes some time (10 to 15 seconds) to return results for some requests. It's not uncommon to have multiple concurrent requests for the same information. As it stands, I have to process those independently, which makes for quite a bit of unnecessary processing.
I've come up with a design that should allow me to avoid the unnecessary processing, but there's one lingering problem.
Each request has a key that identifies the data being requested. I maintain a dictionary of requests, keyed by the request key. The request object has some state information and a WaitHandle that is used to wait on the results.
When a client calls my Search method, the code checks the dictionary to see if a request already exists for that key. If so, the client just waits on the WaitHandle. If no request exists, I create one, add it to the dictionary, and issue an asynchronous call to get the information. Again, the code waits on the event.
When the asynchronous process has obtained the results, it updates the request object, removes the request from the dictionary, and then signals the event.
This all works great. Except I don't know when to dispose of the request object. That is, since I don't know when the last client is using it, I can't call Dispose on it. I have to wait for the garbage collector to come along and clean up.
Here's the code:
class SearchRequest: IDisposable
{
public readonly string RequestKey;
public string Results { get; set; }
public ManualResetEvent WaitEvent { get; private set; }
public SearchRequest(string key)
{
RequestKey = key;
WaitEvent = new ManualResetEvent(false);
}
public void Dispose()
{
WaitEvent.Dispose();
GC.SuppressFinalize(this);
}
}
ConcurrentDictionary<string, SearchRequest> Requests = new ConcurrentDictionary<string, SearchRequest>();
string Search(string key)
{
SearchRequest req;
bool addedNew = false;
req = Requests.GetOrAdd(key, (s) =>
{
// Create a new request.
var r = new SearchRequest(s);
Console.WriteLine("Added new request with key {0}", key);
addedNew = true;
return r;
});
if (addedNew)
{
// A new request was created.
// Start a search.
ThreadPool.QueueUserWorkItem((obj) =>
{
// Get the results
req.Results = DoSearch(req.RequestKey); // DoSearch takes several seconds
// Remove the request from the pending list
SearchRequest trash;
Requests.TryRemove(req.RequestKey, out trash);
// And signal that the request is finished
req.WaitEvent.Set();
});
}
Console.WriteLine("Waiting for results from request with key {0}", key);
req.WaitEvent.WaitOne();
return req.Results;
}
Basically, I don't know when the last client will be released. No matter how I slice it here, I have a race condition. Consider:
Thread A Creates a new request, starts Thread 2, and waits on the wait handle.
Thread B Begins processing the request.
Thread C detects that there's a pending request, and then gets swapped out.
Thread B Completes the request, removes the item from the dictionary, and sets the event.
Thread A's wait is satisfied, and it returns the result.
Thread C wakes up, calls WaitOne, is released, and returns the result.
If I use some kind of reference counting so that the "last" client calls Dispose, then the object would be disposed by Thread A in the above scenario. Thread C would then die when it tried to wait on the disposed WaitHandle.
The only way I can see to fix this is to use a reference counting scheme and protect access to the dictionary with a lock (in which case using ConcurrentDictionary is pointless) so that a lookup is always accompanied by an increment of the reference count. Whereas that would work, it seems like an ugly hack.
Another solution would be to ditch the WaitHandle and use an event-like mechanism with callbacks. But that, too, would require me to protect the lookups with a lock, and I have the added complication of dealing with an event or a naked multicast delegate. That seems like a hack, too.
This probably isn't a problem currently, because this application doesn't yet get enough traffic for those abandoned handles to add up before the next GC pass comes and cleans them up. And maybe it won't ever be a problem? It worries me, though, that I'm leaving them to be cleaned up by the GC when I should be calling Dispose to get rid of them.
Ideas? Is this a potential problem? If so, do you have a clean solution?
Consider using Lazy<T> for SearchRequest.Results maybe? But that would probably entail a bit of redesign. Haven't thought this out completely.
But what would probably be almost a drop-in replacement for your use case is to implement your own Wait() and Set() methods in SearchRequest. Something like:
object _resultLock;
void Wait()
{
lock(_resultLock)
{
while (!_hasResult)
Monitor.Wait(_resultLock);
}
}
void Set(string results)
{
lock(_resultLock)
{
Results = results;
_hasResult = true;
Monitor.PulseAll(_resultLock);
}
}
No need to dispose. :)
I think that your best bet to make this work is to use the TPL for all of you multi-threading needs. That's what it is good at.
As per my comment on your question, you need to keep in mind that ConcurrentDictionary does have side-effects. If multiple threads try to call GetOrAdd at the same time then the factory can be invoked for all of them, but only one will win. The values produced for the other threads will just be discarded, however by then the compute has been done.
Since you also said that doing searches is expensive then the cost of taking a lock ad then using a standard dictionary would be minimal.
So this is what I suggest:
private Dictionary<string, Task<string>> _requests
= new Dictionary<string, Task<string>>();
public string Search(string key)
{
Task<string> task;
lock (_requests)
{
if (_requests.ContainsKey(key))
{
task = _requests[key];
}
else
{
task = Task<string>
.Factory
.StartNew(() => DoSearch(key));
_requests[key] = task;
task.ContinueWith(t =>
{
lock(_requests)
{
_requests.Remove(key);
}
});
}
}
return task.Result;
}
This option nicely runs the search, remembers the task throughout the duration of the search and then removes it from the dictionary when it completes. All requests for the same key while a search is executing get the same task and so will get the same result once the task is complete.
I've test the code and it works.

Categories

Resources