Reputation: 1954
I have some doubts whether the "library" method below will actually work, or eventually deadlock.
I have an "old" object which needs to be protected, and "lock" seems to be the best tool, given that there are some reentrant calls to this resource (SomeSharedResource) throughout the "class".
EDIT:
1.) Will a call to "Compute" actually block or worse case deadlock?
var finalResult = await Compute(3).ConfigureAwait(false); // will this block?
2.) How about if someone did this?
var finalResult = Compute(3).Result; // will this block/deadlock?
3.) 2 Threads calling at the same time:
var finalResult = await Compute(3).ConfigureAwait(false); // will this block?
The method in question:
private readonly object lockObject = new object();
private async Task<double> Compute(int input)
{
double result;
lock (lockObject) {
result = SomeSharedResource(input);
}
return await ComputeAsync(result).ConfigureAwait(false); // some other awaitable method
}
Upvotes: 6
Views: 11442
Reputation: 456322
None of these questions have actual answers. The answers to them all are "yes, they can deadlock, given sufficient operating conditions".
If you mean "will the lock
statement cause blocking or deadlock?", then the answer is yes for the blocking and no for the deadlock (assuming SomeSharedResource(input)
is well-behaved, i.e., doesn't deadlock internally).
Upvotes: 2
Reputation: 1572
For questions 1, 2 and 3 the answer is, "They probably won't deadlock". Though, multiple simultaneous calls to a lock
section WILL BLOCK, since they cannot be executed simultaneously, that does not mean they will deadlock.
You need to inspect what is happening inside the lock
to determine that.
The danger is if a function inside the lock
spawns a blocking asynchronous call (await
or equivalent) that ends up calling back to Compute()
or other code that also locks on lockObject
.
As long as none of the functions inside the lock
are blocking for another thread, you should be safe. If there were blocking asynchronous calls, you would need to validate that they don't reenter, or make sure not to block on them.
Here are some examples of what to do, and NOT to do:
private async Task<double> DoSomething()
{
return await Compute(0);
}
private readonly object lockObject = new object();
private async Task<double> Compute(int input)
{
double result;
lock (lockObject) {
//This would be bad.
result = await DoSomething();
//So would this. If you didn't wait inside the lock you wouldn't deadlock though.
var t = DoSomething();
result = t.Result;
//This would be okay because it doesn't call back to Compute()
using (var stream = File.OpenText("test.txt"))
{
var contents = await stream.ReadToEndAsync();
}
}
return await ComputeAsync(result).ConfigureAwait(false); // some other awaitable method
}
Upvotes: 1
Reputation: 44066
A call to Compute(int input)
can certainly block. You lock (lockobject)
in a synchronous manner. If one thread holds lockObject and another attempts to a acquire it, the second thread will block while waiting for the first thread to finish.
This can block for the same reason as #1. It will not deadlock based on what we see here because the result continuation does not inherit a synchronization context. It will also block waiting synchronously for the result of ComputeAsync()
because it uses .Result
This can block for the same reason as #1.
Locks and continuations are different concepts. While I can't promise you that your program won't deadlock, it's because I can't see your entire program. Nothing that you've shown here is begging to deadlock (typical caveats re: .Result
aside, of course).
But recognize that the presence of a lock() is practically advertising the possibility of a blocked thread of execution. The async
-await
keywords don't do anything magical with a lock in some synchronously executed code.
Upvotes: 3