ReaderWriterLockSlim questions
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ height:90px;width:728px;box-sizing:border-box;
}
In his answer here, https://stackoverflow.com/a/19664437/4919475
Stephen Cleary mentioned
ReaderWriterLockSlim is a thread-affine lock type, so it usually
cannot be used with async and await.
What did he mean by "usually"? When can ReaderWriterLockSlim
be used?
Also, I've read here http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ that ReaderWriterLockSlim
has different quirks, but this article is from 2007. Did it change since then?
c# asynchronous locking
add a comment |
In his answer here, https://stackoverflow.com/a/19664437/4919475
Stephen Cleary mentioned
ReaderWriterLockSlim is a thread-affine lock type, so it usually
cannot be used with async and await.
What did he mean by "usually"? When can ReaderWriterLockSlim
be used?
Also, I've read here http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ that ReaderWriterLockSlim
has different quirks, but this article is from 2007. Did it change since then?
c# asynchronous locking
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40
add a comment |
In his answer here, https://stackoverflow.com/a/19664437/4919475
Stephen Cleary mentioned
ReaderWriterLockSlim is a thread-affine lock type, so it usually
cannot be used with async and await.
What did he mean by "usually"? When can ReaderWriterLockSlim
be used?
Also, I've read here http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ that ReaderWriterLockSlim
has different quirks, but this article is from 2007. Did it change since then?
c# asynchronous locking
In his answer here, https://stackoverflow.com/a/19664437/4919475
Stephen Cleary mentioned
ReaderWriterLockSlim is a thread-affine lock type, so it usually
cannot be used with async and await.
What did he mean by "usually"? When can ReaderWriterLockSlim
be used?
Also, I've read here http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/ that ReaderWriterLockSlim
has different quirks, but this article is from 2007. Did it change since then?
c# asynchronous locking
c# asynchronous locking
edited May 23 '17 at 12:03
Community♦
11
11
asked May 17 '17 at 23:11
Don BoxDon Box
965721
965721
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40
add a comment |
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40
add a comment |
2 Answers
2
active
oldest
votes
I guess you've posted a question that only Cleary can answer, because you want to know what he means.
In the meantime, the obvious inference from his statement is that you can get away with using ReaderWriterLockSlim
with async
/await
in any situation where you are able to guarantee the same thread that acquired the lock will also be able to release it.
For example, you could imagine code like this:
private readonly ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
async void button1_Click(object sender, EventArgs e)
{
_rwls.EnterWriteLock();
await ...;
_rwls.ExitWriteLock();
}
In the above, because the Click
event will be raised in a thread where await
will return to, you can acquire the lock, execute the await
, and still get away with releasing the lock in the continuation, because you know it'll be the same thread.
In many other uses of async
/await
, the continuation is not guaranteed to be in the thread in which the method yielded, and so it wouldn't be allowed to release the lock having acquired it previous to the await
. In some cases, this is explicitly intentional (i.e. ConfigureAwait(false)
), in other cases it's just a natural outcome of the context of the await
. Either way, those scenarios aren't compatible with ReaderWriterLockSlim
the way the Click
example would be.
(I am intentionally ignoring the larger question of whether it's a good idea to acquire a lock and then hold it for the duration of a potentially long-running asynchronous operation. That is, as they say, "a whole 'nother ball o' wax" .)
Addendum:
A "short" comment, which is too long to be an actual comment, regarding the "larger question" I am ignoring…
The "larger question" is fairly broad and highly context-dependent. It's why I didn't address it. The short version is in two parts:
In general, locks should be held for brief periods of time, but in general asynchronous operations are known to be potentially long in duration, so the two are mutually disagreeable. Locks are a necessary evil when doing concurrent operations, but they will always to some extent negate the benefit of doing things concurrently, because they have the effect of serializing otherwise-concurrent operations.
The longer you hold a lock, the greater the likelihood of one or more threads getting blocked waiting for something, serializing whatever work they have. They are all waiting on the same lock, so even once the long-running lock is released, they still will all have to work in order, not concurrently. It's a bit like a traffic jam where a long queue of cars is waiting for a construction truck to finish blocking the road…even once the truck is out of the way, it will take some significant time to clear the jam.
I would not say is inherently bad to hold a lock during an asynchronous operation — that is, I can imagine carefully thought-out scenarios where it would be okay — but it very often will undermine other implementation goals, and can in some cases completely undo a design meant to be heavily concurrent, especially when done without great care.Semantically it's easy to make a mistake, i.e. with
await
you know the lock remains for the duration, but "fire-and-forget" is not uncommon, and would lead to the code appearing to lock while an asynchronous operation is occurring, but in reality it not (see the Stack Overflow question What happens to a lock during an Invoke/BeginInvoke? (event dispatching) for an example of someone who did exactly this, and didn't even realize it). One methodology for avoiding buggy code is to simply avoid patterns of coding known to potentially lead to bugs.
Again, if one is careful enough, one can avoid the bug. But it is generally better to simply change the implementation to use a less tricky approach, and to be in the habit of doing so.
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
add a comment |
I noticed over on this question that you had asked:
Can you explain what you mean by "arbitrary code"?
I believe this note highlights an important aspect to "the larger question" which I will try--briefly, as I am also pressed for time--to address here. One of the main concerns here is that an await statement cannot guarantee the Task
it awaits will run within the same context (particularly, in the case of thread-affine locks, on the same thread) as the calling code; this, in fact, would defeat much of the purpose of the Task
promise.
Let's say the Task
you await, somewhere down the line, awaits a Task
created using Task.Run
, is otherwise on another thread, or has yielded the current thread to await some background resource (like disk or network I/O). Under these conditions there are at least two unexpected behaviors which would be easy to accidentally come across:
If the code executing in the other thread attempts to obtain the same lock as the calling code that is awaiting it; the calling thread owns the lock and since the sub-task is executing on a different thread it cannot obtain the lock until the calling thread releases it, which it will not do because it is awaiting the sub-task that has not completed. If the second attempt to lock was on the same thread as the first, the lock would recognize that this thread has already acquired the lock and would allow the second lock attempt to proceed. Since they are not on the same thread this becomes a self-dependent deadlock and will either halt both the calling thread and the sub-task or will timeout, depending on the locking methods used. Most other deadlocks require using 2 or more locks in differing order across multiple code paths where each path holds a lock the other is waiting on.
If the calling thread is the UI thread (or some other context with a message pump which can continue processing requests while a previous request is awaiting asynchronous behavior), assuming it awaits a
Task
executing in another thread which takes long enough to process that the message pump begins processing another message (like another click to the same button, or any other "arbitrary code" which might want the same lock), that new message is executing on the same thread which owns the lock and is therefore allowed to proceed even though the previousTask
has not completed, thus allowing arbitrary access to resources that are supposed to be synchronized.
While the former could cause your application or some component of it to lock up, the latter of these issues can yield very unexpected results and be especially tricky to troubleshoot. Similar conditions exist for all thread-affine locking mechanisms (like Monitor
which is the underlying implementation of the lock
keyword). Hope that helps.
If you're interested in more about parallelism patterns in C#, I might recommend the free Threading in C# e-book (which is actually an excerpt from the otherwise excellent book "C# in a Nutshell")
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f44036160%2freaderwriterlockslim-questions%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
I guess you've posted a question that only Cleary can answer, because you want to know what he means.
In the meantime, the obvious inference from his statement is that you can get away with using ReaderWriterLockSlim
with async
/await
in any situation where you are able to guarantee the same thread that acquired the lock will also be able to release it.
For example, you could imagine code like this:
private readonly ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
async void button1_Click(object sender, EventArgs e)
{
_rwls.EnterWriteLock();
await ...;
_rwls.ExitWriteLock();
}
In the above, because the Click
event will be raised in a thread where await
will return to, you can acquire the lock, execute the await
, and still get away with releasing the lock in the continuation, because you know it'll be the same thread.
In many other uses of async
/await
, the continuation is not guaranteed to be in the thread in which the method yielded, and so it wouldn't be allowed to release the lock having acquired it previous to the await
. In some cases, this is explicitly intentional (i.e. ConfigureAwait(false)
), in other cases it's just a natural outcome of the context of the await
. Either way, those scenarios aren't compatible with ReaderWriterLockSlim
the way the Click
example would be.
(I am intentionally ignoring the larger question of whether it's a good idea to acquire a lock and then hold it for the duration of a potentially long-running asynchronous operation. That is, as they say, "a whole 'nother ball o' wax" .)
Addendum:
A "short" comment, which is too long to be an actual comment, regarding the "larger question" I am ignoring…
The "larger question" is fairly broad and highly context-dependent. It's why I didn't address it. The short version is in two parts:
In general, locks should be held for brief periods of time, but in general asynchronous operations are known to be potentially long in duration, so the two are mutually disagreeable. Locks are a necessary evil when doing concurrent operations, but they will always to some extent negate the benefit of doing things concurrently, because they have the effect of serializing otherwise-concurrent operations.
The longer you hold a lock, the greater the likelihood of one or more threads getting blocked waiting for something, serializing whatever work they have. They are all waiting on the same lock, so even once the long-running lock is released, they still will all have to work in order, not concurrently. It's a bit like a traffic jam where a long queue of cars is waiting for a construction truck to finish blocking the road…even once the truck is out of the way, it will take some significant time to clear the jam.
I would not say is inherently bad to hold a lock during an asynchronous operation — that is, I can imagine carefully thought-out scenarios where it would be okay — but it very often will undermine other implementation goals, and can in some cases completely undo a design meant to be heavily concurrent, especially when done without great care.Semantically it's easy to make a mistake, i.e. with
await
you know the lock remains for the duration, but "fire-and-forget" is not uncommon, and would lead to the code appearing to lock while an asynchronous operation is occurring, but in reality it not (see the Stack Overflow question What happens to a lock during an Invoke/BeginInvoke? (event dispatching) for an example of someone who did exactly this, and didn't even realize it). One methodology for avoiding buggy code is to simply avoid patterns of coding known to potentially lead to bugs.
Again, if one is careful enough, one can avoid the bug. But it is generally better to simply change the implementation to use a less tricky approach, and to be in the habit of doing so.
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
add a comment |
I guess you've posted a question that only Cleary can answer, because you want to know what he means.
In the meantime, the obvious inference from his statement is that you can get away with using ReaderWriterLockSlim
with async
/await
in any situation where you are able to guarantee the same thread that acquired the lock will also be able to release it.
For example, you could imagine code like this:
private readonly ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
async void button1_Click(object sender, EventArgs e)
{
_rwls.EnterWriteLock();
await ...;
_rwls.ExitWriteLock();
}
In the above, because the Click
event will be raised in a thread where await
will return to, you can acquire the lock, execute the await
, and still get away with releasing the lock in the continuation, because you know it'll be the same thread.
In many other uses of async
/await
, the continuation is not guaranteed to be in the thread in which the method yielded, and so it wouldn't be allowed to release the lock having acquired it previous to the await
. In some cases, this is explicitly intentional (i.e. ConfigureAwait(false)
), in other cases it's just a natural outcome of the context of the await
. Either way, those scenarios aren't compatible with ReaderWriterLockSlim
the way the Click
example would be.
(I am intentionally ignoring the larger question of whether it's a good idea to acquire a lock and then hold it for the duration of a potentially long-running asynchronous operation. That is, as they say, "a whole 'nother ball o' wax" .)
Addendum:
A "short" comment, which is too long to be an actual comment, regarding the "larger question" I am ignoring…
The "larger question" is fairly broad and highly context-dependent. It's why I didn't address it. The short version is in two parts:
In general, locks should be held for brief periods of time, but in general asynchronous operations are known to be potentially long in duration, so the two are mutually disagreeable. Locks are a necessary evil when doing concurrent operations, but they will always to some extent negate the benefit of doing things concurrently, because they have the effect of serializing otherwise-concurrent operations.
The longer you hold a lock, the greater the likelihood of one or more threads getting blocked waiting for something, serializing whatever work they have. They are all waiting on the same lock, so even once the long-running lock is released, they still will all have to work in order, not concurrently. It's a bit like a traffic jam where a long queue of cars is waiting for a construction truck to finish blocking the road…even once the truck is out of the way, it will take some significant time to clear the jam.
I would not say is inherently bad to hold a lock during an asynchronous operation — that is, I can imagine carefully thought-out scenarios where it would be okay — but it very often will undermine other implementation goals, and can in some cases completely undo a design meant to be heavily concurrent, especially when done without great care.Semantically it's easy to make a mistake, i.e. with
await
you know the lock remains for the duration, but "fire-and-forget" is not uncommon, and would lead to the code appearing to lock while an asynchronous operation is occurring, but in reality it not (see the Stack Overflow question What happens to a lock during an Invoke/BeginInvoke? (event dispatching) for an example of someone who did exactly this, and didn't even realize it). One methodology for avoiding buggy code is to simply avoid patterns of coding known to potentially lead to bugs.
Again, if one is careful enough, one can avoid the bug. But it is generally better to simply change the implementation to use a less tricky approach, and to be in the habit of doing so.
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
add a comment |
I guess you've posted a question that only Cleary can answer, because you want to know what he means.
In the meantime, the obvious inference from his statement is that you can get away with using ReaderWriterLockSlim
with async
/await
in any situation where you are able to guarantee the same thread that acquired the lock will also be able to release it.
For example, you could imagine code like this:
private readonly ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
async void button1_Click(object sender, EventArgs e)
{
_rwls.EnterWriteLock();
await ...;
_rwls.ExitWriteLock();
}
In the above, because the Click
event will be raised in a thread where await
will return to, you can acquire the lock, execute the await
, and still get away with releasing the lock in the continuation, because you know it'll be the same thread.
In many other uses of async
/await
, the continuation is not guaranteed to be in the thread in which the method yielded, and so it wouldn't be allowed to release the lock having acquired it previous to the await
. In some cases, this is explicitly intentional (i.e. ConfigureAwait(false)
), in other cases it's just a natural outcome of the context of the await
. Either way, those scenarios aren't compatible with ReaderWriterLockSlim
the way the Click
example would be.
(I am intentionally ignoring the larger question of whether it's a good idea to acquire a lock and then hold it for the duration of a potentially long-running asynchronous operation. That is, as they say, "a whole 'nother ball o' wax" .)
Addendum:
A "short" comment, which is too long to be an actual comment, regarding the "larger question" I am ignoring…
The "larger question" is fairly broad and highly context-dependent. It's why I didn't address it. The short version is in two parts:
In general, locks should be held for brief periods of time, but in general asynchronous operations are known to be potentially long in duration, so the two are mutually disagreeable. Locks are a necessary evil when doing concurrent operations, but they will always to some extent negate the benefit of doing things concurrently, because they have the effect of serializing otherwise-concurrent operations.
The longer you hold a lock, the greater the likelihood of one or more threads getting blocked waiting for something, serializing whatever work they have. They are all waiting on the same lock, so even once the long-running lock is released, they still will all have to work in order, not concurrently. It's a bit like a traffic jam where a long queue of cars is waiting for a construction truck to finish blocking the road…even once the truck is out of the way, it will take some significant time to clear the jam.
I would not say is inherently bad to hold a lock during an asynchronous operation — that is, I can imagine carefully thought-out scenarios where it would be okay — but it very often will undermine other implementation goals, and can in some cases completely undo a design meant to be heavily concurrent, especially when done without great care.Semantically it's easy to make a mistake, i.e. with
await
you know the lock remains for the duration, but "fire-and-forget" is not uncommon, and would lead to the code appearing to lock while an asynchronous operation is occurring, but in reality it not (see the Stack Overflow question What happens to a lock during an Invoke/BeginInvoke? (event dispatching) for an example of someone who did exactly this, and didn't even realize it). One methodology for avoiding buggy code is to simply avoid patterns of coding known to potentially lead to bugs.
Again, if one is careful enough, one can avoid the bug. But it is generally better to simply change the implementation to use a less tricky approach, and to be in the habit of doing so.
I guess you've posted a question that only Cleary can answer, because you want to know what he means.
In the meantime, the obvious inference from his statement is that you can get away with using ReaderWriterLockSlim
with async
/await
in any situation where you are able to guarantee the same thread that acquired the lock will also be able to release it.
For example, you could imagine code like this:
private readonly ReaderWriterLockSlim _rwls = new ReaderWriterLockSlim();
async void button1_Click(object sender, EventArgs e)
{
_rwls.EnterWriteLock();
await ...;
_rwls.ExitWriteLock();
}
In the above, because the Click
event will be raised in a thread where await
will return to, you can acquire the lock, execute the await
, and still get away with releasing the lock in the continuation, because you know it'll be the same thread.
In many other uses of async
/await
, the continuation is not guaranteed to be in the thread in which the method yielded, and so it wouldn't be allowed to release the lock having acquired it previous to the await
. In some cases, this is explicitly intentional (i.e. ConfigureAwait(false)
), in other cases it's just a natural outcome of the context of the await
. Either way, those scenarios aren't compatible with ReaderWriterLockSlim
the way the Click
example would be.
(I am intentionally ignoring the larger question of whether it's a good idea to acquire a lock and then hold it for the duration of a potentially long-running asynchronous operation. That is, as they say, "a whole 'nother ball o' wax" .)
Addendum:
A "short" comment, which is too long to be an actual comment, regarding the "larger question" I am ignoring…
The "larger question" is fairly broad and highly context-dependent. It's why I didn't address it. The short version is in two parts:
In general, locks should be held for brief periods of time, but in general asynchronous operations are known to be potentially long in duration, so the two are mutually disagreeable. Locks are a necessary evil when doing concurrent operations, but they will always to some extent negate the benefit of doing things concurrently, because they have the effect of serializing otherwise-concurrent operations.
The longer you hold a lock, the greater the likelihood of one or more threads getting blocked waiting for something, serializing whatever work they have. They are all waiting on the same lock, so even once the long-running lock is released, they still will all have to work in order, not concurrently. It's a bit like a traffic jam where a long queue of cars is waiting for a construction truck to finish blocking the road…even once the truck is out of the way, it will take some significant time to clear the jam.
I would not say is inherently bad to hold a lock during an asynchronous operation — that is, I can imagine carefully thought-out scenarios where it would be okay — but it very often will undermine other implementation goals, and can in some cases completely undo a design meant to be heavily concurrent, especially when done without great care.Semantically it's easy to make a mistake, i.e. with
await
you know the lock remains for the duration, but "fire-and-forget" is not uncommon, and would lead to the code appearing to lock while an asynchronous operation is occurring, but in reality it not (see the Stack Overflow question What happens to a lock during an Invoke/BeginInvoke? (event dispatching) for an example of someone who did exactly this, and didn't even realize it). One methodology for avoiding buggy code is to simply avoid patterns of coding known to potentially lead to bugs.
Again, if one is careful enough, one can avoid the bug. But it is generally better to simply change the implementation to use a less tricky approach, and to be in the habit of doing so.
edited May 23 '17 at 12:26
Community♦
11
11
answered May 18 '17 at 0:14
Peter DunihoPeter Duniho
49.2k44987
49.2k44987
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
add a comment |
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
Thanks! Can you please not intentionally ignore the larger question if it's a good Idea to acquire a lock and then hold it during async? :) If you could detail that a bit more I'd appreciate that! Thanks again
– Don Box
May 18 '17 at 7:19
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
@Don: please see edit. I think that's about as much as is worth mentioning for now, without more context and some specific scenario in mind.
– Peter Duniho
May 18 '17 at 17:33
add a comment |
I noticed over on this question that you had asked:
Can you explain what you mean by "arbitrary code"?
I believe this note highlights an important aspect to "the larger question" which I will try--briefly, as I am also pressed for time--to address here. One of the main concerns here is that an await statement cannot guarantee the Task
it awaits will run within the same context (particularly, in the case of thread-affine locks, on the same thread) as the calling code; this, in fact, would defeat much of the purpose of the Task
promise.
Let's say the Task
you await, somewhere down the line, awaits a Task
created using Task.Run
, is otherwise on another thread, or has yielded the current thread to await some background resource (like disk or network I/O). Under these conditions there are at least two unexpected behaviors which would be easy to accidentally come across:
If the code executing in the other thread attempts to obtain the same lock as the calling code that is awaiting it; the calling thread owns the lock and since the sub-task is executing on a different thread it cannot obtain the lock until the calling thread releases it, which it will not do because it is awaiting the sub-task that has not completed. If the second attempt to lock was on the same thread as the first, the lock would recognize that this thread has already acquired the lock and would allow the second lock attempt to proceed. Since they are not on the same thread this becomes a self-dependent deadlock and will either halt both the calling thread and the sub-task or will timeout, depending on the locking methods used. Most other deadlocks require using 2 or more locks in differing order across multiple code paths where each path holds a lock the other is waiting on.
If the calling thread is the UI thread (or some other context with a message pump which can continue processing requests while a previous request is awaiting asynchronous behavior), assuming it awaits a
Task
executing in another thread which takes long enough to process that the message pump begins processing another message (like another click to the same button, or any other "arbitrary code" which might want the same lock), that new message is executing on the same thread which owns the lock and is therefore allowed to proceed even though the previousTask
has not completed, thus allowing arbitrary access to resources that are supposed to be synchronized.
While the former could cause your application or some component of it to lock up, the latter of these issues can yield very unexpected results and be especially tricky to troubleshoot. Similar conditions exist for all thread-affine locking mechanisms (like Monitor
which is the underlying implementation of the lock
keyword). Hope that helps.
If you're interested in more about parallelism patterns in C#, I might recommend the free Threading in C# e-book (which is actually an excerpt from the otherwise excellent book "C# in a Nutshell")
add a comment |
I noticed over on this question that you had asked:
Can you explain what you mean by "arbitrary code"?
I believe this note highlights an important aspect to "the larger question" which I will try--briefly, as I am also pressed for time--to address here. One of the main concerns here is that an await statement cannot guarantee the Task
it awaits will run within the same context (particularly, in the case of thread-affine locks, on the same thread) as the calling code; this, in fact, would defeat much of the purpose of the Task
promise.
Let's say the Task
you await, somewhere down the line, awaits a Task
created using Task.Run
, is otherwise on another thread, or has yielded the current thread to await some background resource (like disk or network I/O). Under these conditions there are at least two unexpected behaviors which would be easy to accidentally come across:
If the code executing in the other thread attempts to obtain the same lock as the calling code that is awaiting it; the calling thread owns the lock and since the sub-task is executing on a different thread it cannot obtain the lock until the calling thread releases it, which it will not do because it is awaiting the sub-task that has not completed. If the second attempt to lock was on the same thread as the first, the lock would recognize that this thread has already acquired the lock and would allow the second lock attempt to proceed. Since they are not on the same thread this becomes a self-dependent deadlock and will either halt both the calling thread and the sub-task or will timeout, depending on the locking methods used. Most other deadlocks require using 2 or more locks in differing order across multiple code paths where each path holds a lock the other is waiting on.
If the calling thread is the UI thread (or some other context with a message pump which can continue processing requests while a previous request is awaiting asynchronous behavior), assuming it awaits a
Task
executing in another thread which takes long enough to process that the message pump begins processing another message (like another click to the same button, or any other "arbitrary code" which might want the same lock), that new message is executing on the same thread which owns the lock and is therefore allowed to proceed even though the previousTask
has not completed, thus allowing arbitrary access to resources that are supposed to be synchronized.
While the former could cause your application or some component of it to lock up, the latter of these issues can yield very unexpected results and be especially tricky to troubleshoot. Similar conditions exist for all thread-affine locking mechanisms (like Monitor
which is the underlying implementation of the lock
keyword). Hope that helps.
If you're interested in more about parallelism patterns in C#, I might recommend the free Threading in C# e-book (which is actually an excerpt from the otherwise excellent book "C# in a Nutshell")
add a comment |
I noticed over on this question that you had asked:
Can you explain what you mean by "arbitrary code"?
I believe this note highlights an important aspect to "the larger question" which I will try--briefly, as I am also pressed for time--to address here. One of the main concerns here is that an await statement cannot guarantee the Task
it awaits will run within the same context (particularly, in the case of thread-affine locks, on the same thread) as the calling code; this, in fact, would defeat much of the purpose of the Task
promise.
Let's say the Task
you await, somewhere down the line, awaits a Task
created using Task.Run
, is otherwise on another thread, or has yielded the current thread to await some background resource (like disk or network I/O). Under these conditions there are at least two unexpected behaviors which would be easy to accidentally come across:
If the code executing in the other thread attempts to obtain the same lock as the calling code that is awaiting it; the calling thread owns the lock and since the sub-task is executing on a different thread it cannot obtain the lock until the calling thread releases it, which it will not do because it is awaiting the sub-task that has not completed. If the second attempt to lock was on the same thread as the first, the lock would recognize that this thread has already acquired the lock and would allow the second lock attempt to proceed. Since they are not on the same thread this becomes a self-dependent deadlock and will either halt both the calling thread and the sub-task or will timeout, depending on the locking methods used. Most other deadlocks require using 2 or more locks in differing order across multiple code paths where each path holds a lock the other is waiting on.
If the calling thread is the UI thread (or some other context with a message pump which can continue processing requests while a previous request is awaiting asynchronous behavior), assuming it awaits a
Task
executing in another thread which takes long enough to process that the message pump begins processing another message (like another click to the same button, or any other "arbitrary code" which might want the same lock), that new message is executing on the same thread which owns the lock and is therefore allowed to proceed even though the previousTask
has not completed, thus allowing arbitrary access to resources that are supposed to be synchronized.
While the former could cause your application or some component of it to lock up, the latter of these issues can yield very unexpected results and be especially tricky to troubleshoot. Similar conditions exist for all thread-affine locking mechanisms (like Monitor
which is the underlying implementation of the lock
keyword). Hope that helps.
If you're interested in more about parallelism patterns in C#, I might recommend the free Threading in C# e-book (which is actually an excerpt from the otherwise excellent book "C# in a Nutshell")
I noticed over on this question that you had asked:
Can you explain what you mean by "arbitrary code"?
I believe this note highlights an important aspect to "the larger question" which I will try--briefly, as I am also pressed for time--to address here. One of the main concerns here is that an await statement cannot guarantee the Task
it awaits will run within the same context (particularly, in the case of thread-affine locks, on the same thread) as the calling code; this, in fact, would defeat much of the purpose of the Task
promise.
Let's say the Task
you await, somewhere down the line, awaits a Task
created using Task.Run
, is otherwise on another thread, or has yielded the current thread to await some background resource (like disk or network I/O). Under these conditions there are at least two unexpected behaviors which would be easy to accidentally come across:
If the code executing in the other thread attempts to obtain the same lock as the calling code that is awaiting it; the calling thread owns the lock and since the sub-task is executing on a different thread it cannot obtain the lock until the calling thread releases it, which it will not do because it is awaiting the sub-task that has not completed. If the second attempt to lock was on the same thread as the first, the lock would recognize that this thread has already acquired the lock and would allow the second lock attempt to proceed. Since they are not on the same thread this becomes a self-dependent deadlock and will either halt both the calling thread and the sub-task or will timeout, depending on the locking methods used. Most other deadlocks require using 2 or more locks in differing order across multiple code paths where each path holds a lock the other is waiting on.
If the calling thread is the UI thread (or some other context with a message pump which can continue processing requests while a previous request is awaiting asynchronous behavior), assuming it awaits a
Task
executing in another thread which takes long enough to process that the message pump begins processing another message (like another click to the same button, or any other "arbitrary code" which might want the same lock), that new message is executing on the same thread which owns the lock and is therefore allowed to proceed even though the previousTask
has not completed, thus allowing arbitrary access to resources that are supposed to be synchronized.
While the former could cause your application or some component of it to lock up, the latter of these issues can yield very unexpected results and be especially tricky to troubleshoot. Similar conditions exist for all thread-affine locking mechanisms (like Monitor
which is the underlying implementation of the lock
keyword). Hope that helps.
If you're interested in more about parallelism patterns in C#, I might recommend the free Threading in C# e-book (which is actually an excerpt from the otherwise excellent book "C# in a Nutshell")
edited Nov 16 '18 at 14:37
answered Nov 16 '18 at 14:32
TheXenocideTheXenocide
763516
763516
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f44036160%2freaderwriterlockslim-questions%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Meh, holding on to a RWL lock across an await is a pretty bad practice. In practice almost nobody does this and if you ever goof it by accident then a runtime exception will warn you about it.
– Hans Passant
May 18 '17 at 17:40