C
C#3mo ago
maxmahem

Async events firing out of expected sequence.

Obviously I've got some assumptions wrong here but my thinking was that in these sets of methods would still fire in sequence:
public async Task StopService() {
if (this.service is null) throw new NullReferenceException();

await this.service.StopAsync();

this.service.Dispose();
this.service = null;

this.logStream?.Flush();
this.logStream?.Dispose();
this.logFileStream?.Dispose();

OnPropertyChanged(nameof(ServiceRunning));
OnPropertyChanged(nameof(CanServiceStart));
}
public async Task StopService() {
if (this.service is null) throw new NullReferenceException();

await this.service.StopAsync();

this.service.Dispose();
this.service = null;

this.logStream?.Flush();
this.logStream?.Dispose();
this.logFileStream?.Dispose();

OnPropertyChanged(nameof(ServiceRunning));
OnPropertyChanged(nameof(CanServiceStart));
}
this.service.StopAsync() calls...
public async Task StopAsync()
{
if (this.cancellationTokenSource is not null) await this.cancellationTokenSource.CancelAsync();
this.listener.Stop();
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped.");
}
public async Task StopAsync()
{
if (this.cancellationTokenSource is not null) await this.cancellationTokenSource.CancelAsync();
this.listener.Stop();
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped.");
}
And my thinking was that StopService would halt on that await until StopAsync returned, but it must not be so because the OnMessage is firing after the dispose methods. So... something isn't right. I can probably turn StopAsync synchronous but I'd rather figure out WTF is going on here.
21 Replies
Yawnder
Yawnder2mo ago
@maxmahem Does it happen if this.cancellationTokenSource is not null too? Also, are you saying OnMessage is getting called after, or that the result of OnMessage is received after? What are you basing that on?
maxmahem
maxmahem2mo ago
cancellationTokenSource always has a value, the guard is just for completeness. OnMessage writes to logFileStream and I'm getting an exception that the stream is disposed. Although... hmm. I think I know what the problem was. I've changed the code here for a different reason, let me retest.
Yawnder
Yawnder2mo ago
Often there is some pooling in the logging. So it might run deferred, then crash.
maxmahem
maxmahem2mo ago
hmm... nope. I was calling OnMessage in a different thread for other reasons previously, thought that might be the issue but I've changed that and the problem persists. Logging is still pretty basic ATM so nothing should be defered here:
public void OnMessage(object? sender, EndPointMessage message)
{
this.messages.Add(message);
if (LoggingEnabled)
this.logStream?.WriteLine($"{message.EndPoint}, {message.Time:s}, {message.Message}");
}
public void OnMessage(object? sender, EndPointMessage message)
{
this.messages.Add(message);
if (LoggingEnabled)
this.logStream?.WriteLine($"{message.EndPoint}, {message.Time:s}, {message.Message}");
}
Yawnder
Yawnder2mo ago
What's logStream?
maxmahem
maxmahem2mo ago
FileStream sorry, I lied. StreamWriter.
Yawnder
Yawnder2mo ago
Try flushing it after every writeline, just for fun (to remove that from the equation)
maxmahem
maxmahem2mo ago
no joy
Yawnder
Yawnder2mo ago
I'm clueless then, sorry Can you put a breakpoint with an "on count" condition to stop if there is a 2nd call to StopService? Also, what is your exception exactly? (just to double check)
maxmahem
maxmahem2mo ago
ObjectDisposedException (no double entry into StopService either) It's getting triggered by this line here:
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped.");
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped.");
boiled goose
boiled goose2mo ago
are you sure StopAsync is not being called twice?
maxmahem
maxmahem2mo ago
100%
Yawnder
Yawnder2mo ago
@maxmahem
this.listener.Stop(); // <-- You stop it here, then...
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped."); // Use it here?
this.listener.Stop(); // <-- You stop it here, then...
OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped."); // Use it here?
Because what's disposed is this.listener right? (and it's the .LocalEndpoint that throws)
maxmahem
maxmahem2mo ago
no, that's the um TcpListner different thing
Yawnder
Yawnder2mo ago
But isn't it what throws the exception? Change OnMessage(this.listener.LocalEndpoint, $"{Name} Service Stopped."); to
var aa = this.listener;
var bb = aa.LocalEndpoint;
OnMessage(bb, $"{Name} Service Stopped.");
var aa = this.listener;
var bb = aa.LocalEndpoint;
OnMessage(bb, $"{Name} Service Stopped.");
If you inspect, you'll see that this.listener is what's already disposed.
maxmahem
maxmahem2mo ago
no no, sorry for the confusion. OnMessage is just where it bubbles out from. The exception originates from there. It's nothing to do with the listner, I am sure. The exception tells me it's because of the Stream being disposed.
Yawnder
Yawnder2mo ago
Oh, ok Do you know if it's the streamwriter or the underlying stream that's being disposed? (I know you said Stream, but I just want to confirm)
maxmahem
maxmahem2mo ago
I think its still due to threading shennigangs. I did reformulate this, but looking deeper the call is still being processed on a different thread so... let me poke around at it.
Yawnder
Yawnder2mo ago
Alright then! I'll have to go to bed anywyas, it's already 2:21
maxmahem
maxmahem2mo ago
hmmm... no I thought it might be related to this, but it's not:
this.service.MessageReceived += (sender, message) => Dispatcher.UIThread.Post(() => OnMessage(sender, message));
this.service.MessageReceived += (sender, message) => Dispatcher.UIThread.Post(() => OnMessage(sender, message));
(same error if called without the Dispatcher)
boiled goose
boiled goose2mo ago
can you put breakpoints or Debug.WriteLine entering and exiting both StopAsync and OnMessage to check that the order of the operations is what you expect?