SynchronizationLockException on Monitor.Exit when using await
You can't await
a task inside a lock
scope (which is syntactic sugar for Monitor.Enter
and Monitor.Exit
). Using a Monitor
directly will fool the compiler but not the framework.
async-await
has no thread-affinity like a Monitor
does. The code after the await
will probably run in a different thread than the code before it. Which means that the thread that releases the Monitor
isn't necessarily the one that acquired it.
Either don't use async-await
in this case, or use a different synchronization construct like SemaphoreSlim
or an AsyncLock
you can build yourself. Here's mine: https://stackoverflow.com/a/21011273/885318
why monitor.exit does not work in another work?
Monitor
has thread-affinity. You can't exit it from a different thread.
But it's not clear what you're trying to do here anyway. You're already in an async method, calling another async method. Just use await
- just setup an synchronization context to allow you to use await
properly in a console application. Or, use Task.Wait
if you're not worried about deadlocks :)
As for Monitor.Wait
, it doesn't do what you think it does. The wait is for a signal, not for the monitor being exited. For example:
static readonly object syncObject = new object();
void Thread1()
{
lock (syncObject) Monitor.Wait(syncObject);
}
void Thread2()
{
lock (syncObject) Monitor.Pulse(syncObject);
}
Both methods are executed on separate threads in this scenario. If Thread1
runs first, it will take the lock, wait for a signal (this exits the lock until the signal is presented) and reacquire the lock after the signal is given. Thread1
provides the signal using the Monitor.Pulse
method. Note that in both cases, the lock is taken on a single thread, and exited on the same thread.
This mechanism isn't very easy to use properly, and is somewhat slow, so you're not going to see it much.
Also, the threads used by Task.Run
aren't yours. It's usually undesirable to use blocking code on thread-pool threads - make sure you understand the trade-offs your making. Even more importantly, tasks have no thread-affinity - so using a thread-affine synchronization primitive like a monitor is rather adventurous :) In your case, even if you took the lock inside the Task.Run
rather than the outside, it would be possible for the Monitor.Exit
to fail, since you might have gotten a different thread after the await
.
Don't forget that the trickiest part about multi-threading isn't that it just doesn't work - it's that it has an ugly tendency to mostly work for the most part, and fail in weird scenarios (that happen all the time in practice, mind you). Testing isn't quite enough to give you much confidence in how a multi-threaded piece of code behaves in practice. All you get is tons of tiny, almost impossible to reproduce bugs. You got lucky here - your code fails reliably; that certainly isn't the case with most multi-threading issues :)
Joe Albahari's introduction to threading is a huge help for multi-threaded programming of any kind. I highly recommend reading through the whole of it at least once. Keep it in your bookmarks for future reference :)
SynchronizationLockException after await
As you've noticed, the Monitor.Enter/Exit are thread-affine.
In their series on creating async-friendly coordination primitives, Stephen includes an AsyncLock that should be what you're looking for AFAICT.
Exception on Monitor.Exit in C#
It should be very clear why you are getting the exception in your new code. If the lock is taken then the object that is unlocked is not the object that was locked. Locks take an object, not a variable. The correct translation of the deeply wrong original code is
// THIS CODE IS COMPLETELY WRONG; DO NOT USE IT
if (_myVar != null)
{
bool lockTaken = false;
var locker = _myVar;
try
{
Monitor.TryEnter(locker, new TimeSpan(0, 0, 5), ref lockTaken);
if (lockTaken)
{
_myVar = newMyVar; // where newMyVar is another List<myClass>
}
}
finally
{
if (lockTaken) Monitor.Exit(locker);
}
}
Which will not throw on exit, but is still completely wrong.
Never lock on the contents of a variable and then mutate the variable; every subsequent lock will lock on a different object! So you have no mutual exclusion.
And never lock on a public object! If that list leaks out anywhere then other wrong code can be locking on that list in an unexpected order, which means deadlocks -- which is the original symptom you are diagnosing.
The correct practice for locking on a field is to create a private readonly object field used only as a locker, and used every time the field is accessed. That way you know that (1) the field is always accessed under the same lock, no matter its value, and (2) the lock object is used only for locking that field, and not for locking something else. That ensures mutual exclusion and prevents deadlocks.
The fact that someone wrote a large multithreaded program without understanding the most basic facts about locks means that it is almost certainly a complete mess of hard-to-find bugs. The fact that this wasn't immediately obvious upon reading the code means that you don't have enough knowledge of threading to fix the problems correctly. You're going to need to either find an expert on this stuff who can help you, or gain at least a minimal working knowledge of correct practices.
I cannot emphasize enough that this is hard stuff. Programs with multiple threads of control in them are extremely difficult to write correctly on modern hardware; many of the things you believe are guaranteed by the language are only guaranteed in single threaded programs.
Monitor.TryEnter / Monitor.Exit and SynchronizationLockException
As you think that to put the calling of Monitor.Exit in try-catch was 'durty'(dirty?), here's a very simple idea trying to 'take the durty away'. Lock is reentrant for the same thread and if one thread acquired successfully, before it releases, attempt from another thread will fail. So that you can consider something like:
public void Exit(object key) {
if(!IsActive) {
return;
}
if(LockDictionary.ContainsKey(key)) {
var syncObject=LockDictionary[key];
if(Monitor.TryEnter(syncObject.SyncObject, 0)) {
SetLockExit(syncObject);
Monitor.Exit(syncObject.SyncObject);
Monitor.Exit(syncObject.SyncObject);
}
}
}
We call Monitor.Exit twice because we lock it twice, one in the code outer, and one just here.
SynchronizationLockException: Cannot wait on monitors on this runtime. - Workaround for TaskCompletionSource in Blazor wasm
I've inspected code by the github link you provided and noticed that you are doing this:
_stringReaderRedirect = new StringReaderRedirect(Read);
where Read
is function mentioned in question. Then inside StringReaderRedirect
you have:
private readonly Func<Task<string>> _ReadRedirectFunc;
public StringReaderRedirect(Func<Task<string>> readredirect) : base("foo")
{
_ReadRedirectFunc = readredirect;
}
And then you do this:
public override string ReadLine()
{
//return _ReadRedirectFunc?.Invoke();
//return base.ReadLine();
Task<string> task = _ReadRedirectFunc?.Invoke();
return task?.GetAwaiter().GetResult();
}
So you are blocking on asynchronous call, which is the source of exception in question. Doing this is a major no-no in a single threaded environment, like Blazor WASM. If exception you see were not thrown then you would have a deadlock: the only thread (UI) is blocked waiting for the result of Read
, while Read
itself depends on user input, for which UI thread is required. There are many similar issues on blazor github repo, for example.
The same would happen in WPF by the way, if you did Read().GetAwaiter().GetResult()
from UI thread. Well not the same, because in case of WPF it would just deadlock, but "will not work" also.
So go async all the way and never block main thread, since it's the only thread you have.
Object synchronization exception only when using breakpoints, in F#
This error throws when you try to exit a lock object you never entered.
You have to write Monitor.Exit(UpdateLock)
when you definetely entered the Monitor.TryEnter(UpdateLock)
.
This can be achieved when you turn the exit into the if
like:
if Monitor.TryEnter(UpdateLock) then
try
try
let response = RestClient.Execute(Request)
response |> ignore
with ex ->
Logging.Error(printfn "%s" ex.Message)
finally
Monitor.Exit(UpdateLock)
I hope this helps.
Appropriate use of Monitor.Exit
Since, someCondition is not passed as a parameter, I could only assume the someCondition could be changing at any time (possibly an instance variable to the class). So, I would write the code this way:
void MethodA()
{
if (someCondition)
{
bool conditionReached = false;
Monitor.Enter(this);
try
{
if (someCondition)
{
conditionReached = true;
}
}
finally
{
Monitor.Exit(this);
}
if (conditionReached)
{
MethodB();
}
}
}
If not, than the previous answer with condition
declared locally would suit you.
Related Topics
Does .Net Have a Built-In Eventargs<T>
Requesting HTML Over Https with C# Webclient
Encoding Trouble with Httpwebresponse
Can't See Localhost from Uwp App
How to Get Modified Date from File in C# on Windows Mobile
Thread with Multiple Parameters
Update Requires a Valid Updatecommand When Passed Datarow Collection with Modified Rows
MVC 5 Dynamic Rows with Begincollectionitem
Multiple Fields Validation Using Remote Validation
Get All Types Implementing Specific Open Generic Type
Check If Datetime Instance Falls in Between Other Two Datetime Objects
Detect Change of Resolution C# Winforms
Visualizing an Ast Created with Antlr (In a .Net Environment)
How to Make Correct Date Format When Writing Data to Excel
Google Maps V3 Geocoding Server-Side
How to Ignore Get-Only Properties in JSON.Net Without Using JSONignore Attributes
Enable-Migrations Exception Calling "Setdata" with "2" Argument(S)