I am writing a little WPF application to test some SignalR code. Everything works from what I have written, but I have stumbled across something which I am unsure of.
I have created an event handler for the HubConnection StateChanged event;
_hub.StateChanged += (change) =>
{
Console.WriteLine("hubConnection.StateChanged {0} => {1}", change.OldState, change.NewState);
if (change.NewState == ConnectionState.Connecting)
{
statusCallBack callBack = new statusCallBack(UpdateStatus);
this.Dispatcher.Invoke(callBack, "hubConnection.StateChanged");
}
if (change.NewState == ConnectionState.Connected)
{
Console.WriteLine("hello");
statusCallBack callBack = new statusCallBack(UpdateStatus);
this.Dispatcher.Invoke(callBack, "hubConnection.StateChanged");
}
};
With my Delegate method statusCallBack and method being;
delegate void statusCallBack(string msg);
private void UpdateStatus(string msg)
{
if (this.Dispatcher.CheckAccess() == true)
{
this.tbStatus.AppendText(Environment.NewLine + DateTime.Now.ToLongTimeString() + " --- " + msg);
this.tbStatus.CaretIndex = this.tbStatus.Text.Length;
this.tbStatus.ScrollToEnd();
}
}
Now I am probably missing something really obvious here, but when in the StateChanged handler I check for ConnectionState.Connecting and output the message to my Label it works fine.
Then when the SignalR HubConnection state then changes to ConnectionState.Connected and I try to Invoke the delegate, the WPF app just locks up.
It will output to the console fine, and check to see if change.NewState == ConnectionState.Connected, then will output "hello" to the console, but then just freezes.
If I debug the app, when it gets to within the Connected if statement, the object change.NewState and change.OldState have the error message below.
View larger image here.
I am lost as to why it works within the first if statement, but not the second. Also why it is able to output the correct values to the console?
Even if I comment out the initial if statement to check for Connecting it will still freeze when Connected.
This is a common deadlock problem when calling back to the UI from an event.
Try using Dispatcher.BeginInvoke rather than Invoke.
As quoted in this thread:
When you use Dispatcher.BeginInvoke it means that it schedules the
given action for execution in the UI thread at a later point in time,
and then returns control to allow the current thread to continue
executing. Invoke blocks the caller until the scheduled action
finishes.
As a side-note: MVVM Light has a very useful helper called DispatcherHelper that is not only compatible with virtually everything (WPF, WinRT, SL, etc.), but very simple to use. It helps you automatically dispatch calls back to the UI. If nothing else, it may be helpful to use the NUGet package JUST for this feature.
Related
I'm using WebView2 in a .net 5 WPF app and have been playing with the devtools protocol as a means of intercepting specific requests for assets. In looking at the Chrome dev docs (https://chromedevtools.github.io/devtools-protocol/), it's possible to intercept requests and then decide whether to continue them, cancel them or satisfy them yourself.
I've been able to successfully intercept the first web request (Eg. https:// www.somedomain.tld), but I've not been able to successfully continue the request (which would presumably trigger any other asset requests made as a result of the parsed html response).
After WebView initialization, I do the following (which works):
// Intercept requests
var receiver = webView.CoreWebView2.GetDevToolsProtocolEventReceiver("Fetch.requestPaused");
receiver.DevToolsProtocolEventReceived += FetchRequestPaused;
await webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.enable", "{}");
This is my event handler - which doesn't do what I'm expecting it to (although it doesn't deadlock now at least):
private void FetchRequestPaused(object sender, Microsoft.Web.WebView2.Core.CoreWebView2DevToolsProtocolEventReceivedEventArgs e)
{
var doc = JsonDocument.Parse(e.ParameterObjectAsJson);
var id = doc.RootElement.GetProperty("requestId");
var payload = $"{{\"requestId\":\"{id}.0\"}}";
// We can't do this as an async call as it will try to post to the main thread, which is
// busy waiting in this event handler, so we deadlock
//_ = await webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.continueRequest", payload);
// Exception: Value does not fall within the expected range.
// var result = await webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.continueRequest", payload).ConfigureAwait(false);
// PROBLEM: This invokes the call on the UI thread OK...
Application.Current.Dispatcher.Invoke(new Action(() =>
{
// ...but it doesn't actually do anything
webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.continueRequest", payload);
}));
}
Not only does the requested page not finish loading, but the browser is left in an unusual state and so right-clicking on the control and selecting "Refresh" will crash - yielding a COMException:
System.Runtime.InteropServices.COMException: 'The group or resource is not in the correct state to perform the requested operation. (0x8007139F)'
Can anyone see what I'm doing wrong here or am missing??
Thanks!
Additional information
In swapping out the events for the deprecated Network.setRequestInterception / Network.continueInterceptedRequest equivalents, I'm seeing the same behaviour - which at least tells us that it's either a problem with my calling code (most likely) or a bug in WebView2 (possible) rather than Chromium.
Any thoughts?
After some more digging, I realised there were two problems. The first is that my installed version of Edge was slightly behind. The second was that my Action delegate was synchronous. The call should read:
// Somebody forgot some 'async' keywords..!
Application.Current.Dispatcher.Invoke(new Action(async () =>
{
var x = await webView.CoreWebView2.CallDevToolsProtocolMethodAsync("Fetch.continueRequest", payload);
}));
We are using Microsoft's UIAutomation framework to develop a client that monitors events of a specific application and responds to them in different ways. We've started with the managed version of the framework, but due to delay issues, moved to the native version wrapped in UIACOMWrapper. After more issues with performance inside our (massive) WPF application, we decided to move it to a separate terminal application (transfer the events to our WPF app through UDP) which seemed to fix all the performance issues. The only problem is that it seems that every several minutes, the events for TabSelection, StructureChanged, WindowOpened and WindowClosed stop being captured for a few minutes. Surprisingly PropertyChanged events are still received and handled while this happens. I will post the relevant code of our event monitor, but this is probably irrelevant as we have seen similar behavior when using Microsoft's own AccEvent utility. I can't post the code of the monitored application as it is proprietary and confidential as well, I can say that it is a WinForms application that hosts WPF windows and also quite massive.
Has anyone seen this sort of behavior while working with the UI Automation framework?
Thank you for your time.
Here's the monitor code (I know the event handling is on the UI Automation threads here but moving it to a dedicated thread did not change anything):
public void registerHandlers()
{
//Register on structure changed and window opened events
System.Windows.Automation.Automation.AddStructureChangedEventHandler(
this.getMsAutomationElement(), System.Windows.Automation.TreeScope.Subtree, this.handleStructureChanged);
System.Windows.Automation.Automation.AddAutomationEventHandler(
System.Windows.Automation.WindowPattern.WindowOpenedEvent,
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree,
this.handleWindowOpened);
System.Windows.Automation.Automation.AddAutomationEventHandler(
System.Windows.Automation.WindowPattern.WindowClosedEvent,
System.Windows.Automation.AutomationElement.RootElement,
System.Windows.Automation.TreeScope.Subtree,
this.handleWindowClosed);
this.registerValueChanged();
this.registerTextNameChange();
this.registerTabSelected();
this.registerRangeValueChanged();
}
private void registerRangeValueChanged()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.RangeValuePattern.ValueProperty);
}
}
private void unregisterRangeValueChanged()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void registerValueChanged()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.ValuePattern.ValueProperty);
}
}
private void unregisterValueChanged()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void registerTextNameChange()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.AutomationElement.NameProperty);
}
}
private void unregisterTextNameChange()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void handleWindowOpened(object src, System.Windows.Automation.AutomationEventArgs e)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window opened:" + " " +
(src as System.Windows.Automation.AutomationElement).Current.Name);
System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement;
//this.sendEventToPluginQueue(src, e, element.GetRuntimeId(), this.getAutomationParent(element).GetRuntimeId());
//Fill out the fields of the control added message
int[] parentId = this.getAutomationParent(element).GetRuntimeId();
this.copyToIcdArray(parentId,
this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.parentRuntimeId);
this.copyToIcdArray(element.GetRuntimeId(),
this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.runtimeId);
//Send the message using the protocol
this.protocol.send(this.protocol.getMessageSet().outgoing.ControlAddedMessage);
}
private void copyToIcdArray(int[] runtimeId, ICD.UI_AUTOMATION.RuntimeId icdRuntimeId)
{
icdRuntimeId.runtimeIdNumberOfItems.setVal((byte)runtimeId.Count());
for (int i = 0; i < runtimeId.Count(); i++)
{
icdRuntimeId.runtimeIdArray.getElement(i).setVal(runtimeId[i]);
}
}
private void handleWindowClosed(object src, System.Windows.Automation.AutomationEventArgs e)
{
if (src != null)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window closed:" + " " +
(src as System.Windows.Automation.AutomationElement).GetRuntimeId().ToString());
System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement;
this.copyToIcdArray(element.GetRuntimeId(),
this.protocol.getMessageSet().outgoing.ControlRemovedMessage.Data.controlRemoved.runtimeId);
//Send the message using the protocol
this.protocol.send(this.protocol.getMessageSet().outgoing.ControlRemovedMessage);
//this.sendEventToPluginQueue(src, e, element.GetRuntimeId());
}
}
EDIT:
I forgot to mention that I strongly suspect that the issue is that one of the UI-Automation event handler threads gets stuck somehow. The reason I believe this, is that when the problem occurred in my monitor, I started an instance of AccEvent and it received all the missing events that my monitor was not getting. This means that the events are being fired but not passed to my monitor.
EDIT2:
I forgot to mention that this happens running in Windows 8 with the specific target application, I have not seen this phenomenon on my own Windows 7 machine with other applications. Another interesting thing is that it seems to happen periodically more or less, but regardless of when I subscribe to events, i.e. it can happen almost immediately after subscribing but then it takes several minutes to reoccur.
I'm afraid I don't know the cause of the delays that you're seeing, but here are some thoughts on this...
Everything I say below relates to the native UIA API in Windows, not the managed .NET UIA API. All improvements to UIA in recent years have been made to the Windows UIA API. So whenever I write UIA client C# code, I call UIA through a managed wrapper that I generate with the tlbimp.exe SDK tool.
That is, I first generate the wrapper with a command like...
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x64\tlbimp.exe" c:\windows\system32\uiautomationcore.dll /out:Interop.UIAutomationCore.dll
Then I include a reference to the Interop.UIAutomationCore.dll in my C# project, add "using Interop.UIAutomationCore;" to my C# file, and then I can do things like...
IUIAutomation uiAutomation = new CUIAutomation8();
IUIAutomationElement rootElement = uiAutomation.GetRootElement();
uiAutomation.AddAutomationEventHandler(
20016, // UIA_Window_WindowOpenedEventId
rootElement,
TreeScope.TreeScope_Descendants,
null,
this);
...
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
{
// Got a window opened event...
}
In Windows 7, there were some important constraints around UIA event handlers. It was easy to write event handlers which didn't account for those constraints, and that could lead to long delays when interacting with UIA. For example, it was important to not add or remove a UIA event handler from inside an event handler. So at the time, I intentionally made no UIA calls at all from inside my event handlers. Instead, I'd post myself a message or add some action to a queue, allow my event handler to return, and take whatever action I wanted to in response to the event shortly afterwards on another thread. This required some more work on my part, but I didn't want to risk hitting delays. And any threads I created would be running in an MTA.
An example of the action described above is in my old focus tracking sample up at https://code.msdn.microsoft.com/windowsapps/Windows-7-UI-Automation-6390614a/sourcecode?fileId=21469&pathId=715901329. The file FocusEventHandler.cs creates the MTA thread and queues messages to avoid making UIA calls inside the event hander.
Since Window 7, I know the constraints in UIA relating to threading and delays have been relaxed, and the likelihood of encountering delays has been reduced. More recently, there were some improvements between Windows 8.1 and Windows 10 in this area, so if it'd be practical to run your code on Windows 10, it would be interesting to see if the delays still repro there.
I know this is time consuming, but you might be interested in removing the interaction with UIA inside your event handlers and seeing if the delays go away. If they do, it'd be a case of determining which action seems to trigger the problem, and seeing if there's an alternative way of achieving your goals without performing the UIA interaction in the event handlers.
For example, in your event handler, you call...
this.getAutomationParent(element).GetRuntimeId();
I expect this will lead to two calls back into the provider app which generated the event. The first call is to get the parent of the source element, and the second call is to get the RuntimeId of that parent. So while UIA is waiting for your event handler to return, you've called twice back into UIA. While I don't know that that's a problem, I'd avoid it.
Sometimes you can avoid a cross-proc call back to the provider process by having some data of interest cached with the event itself. For example, say I know I'm going to want the RuntimeId of an element that raised a WindowOpened event. I can ask UIA to cache that data with the events I receive, when I register for the events.
int propertyRuntimeId = 30000; // UIA_RuntimeIdPropertyId
...
IUIAutomationCacheRequest cacheRequestRuntimeId = uiAutomation.CreateCacheRequest();
cacheRequestRuntimeId.AddProperty(propertyRuntimeId);
uiAutomation.AddAutomationEventHandler(
20016, // UIA_Window_WindowOpenedEventId
rootElement,
TreeScope.TreeScope_Descendants,
cacheRequestRuntimeId,
this);
...
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
{
// Got a window opened event...
// Get the RuntimeId from the source element. Because that data is cached with the
// event, we don't have to call back through UIA into the provider process here.
int[] runtimeId = sender.GetCachedPropertyValue(propertyRuntimeId);
}
On a side note, when practical, I always cache data when dealing with events or accessing elements through UIA, (by using calls such as FindFirstBuildCache(),) as I want to avoid as many cross-proc calls as possible.
So my advice would be:
Use the native Windows UIA API with a managed wrapper generated by tlbimp.exe.
Cache as much data as possible with the events, to avoid having to call back into the provider process unnecessarily later.
Avoid calls back into UIA from inside a UIA event handler.
Thanks,
Guy
I have seen this behavior in my project. The solution was unsubscribes and resubscribe to the events using a timer.
In addition, I set off any action following the events in a new task (running in an STA thread pool).
I have following SignalR code for desktop client:
_hub.On<String>("Ping",(string s) =>
{
Console.WriteLine(s + " from MainHub ");
_hub.Invoke("Acknowledge","Say Hello to MainHub");
});
I have Ping and Acknowledge method on Hub.
But
_hub.Invoke("Acknowledge","Say Hello to MainHub");
is not firing properly from desktop client.
How to write this code properly?
As per this Link
Currently in the .NET client we serialize invoking user callbacks upon receiving messages from the server using a TaskQueue. This includes callbacks registered using HubProxy.On or continuations after HubProxy.Invoke.
If these callbacks block, especially blocking waiting on result from another callback, the receive dispatch queue will simply stop (deadlock).
We should detect this condition and trace an error when it occurs (Connection.OnError).
We could do this in the TaskQueue itself (with an optional flag that enables it of course) that makes it essentially monitor itself, using an async loop started on the first enqueue that checks if the task currently running is the same as what was running on the last interval. If so, log the error.
Now I have managed to fix this issue by a quick but dirty solution. I know this is not the way to fix this issue, but I dont have any choices other than this. Other solution better than this is higly appreciated.
Threading.Timer tmr= new Threading.Timer(new TimerCallback(Acknowledge),null, 0, Timeout.Infinite);
private void Acknowldege(object state)
{
if(SignalRIsReady)//check if signalr is ready
{
_hub.Invoke("Acknowledge","Say Hello to MainHub");
tmr.Change(Timeout.Infinite, Timeout.Infinite);//wait until next event occurs
}
}
_hub.On<String>("Ping",(string s) =>
{
Console.WriteLine(s + " from MainHub ");
tmr.Change(50, Timeout.Infinite);// call callback after 50ms
});
Another Solution I have managed is using configureawait. Following is the solution:
_hub.On<String>("Ping",async(string s) =>
{
Console.WriteLine(s + " from MainHub ");
await Task.Run(async()=>{
_hub.Invoke("Acknowledge","Say Hello to MainHub");
}).ConfigureAwait(false);//this will run the task to seperate thread, unblocking the parent thread
});
The Console.WriteLine() does not output anything to the screen, while the handler SystemEvents_SessionSwitch() is called in blocking computer. But if in the Main() method call at least once the method Console.WriteLine(), then the method in the handler will work. What is the reason for this strange behavior / bug?
I'm using Windows 8 64 bit, .NET Framework 4.0
using System;
using Microsoft.Win32;
namespace TestWindowsEvents
{
class Program
{
static void Main(string[] args)
{
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
//Console.WriteLine("Test"); //if uncomment this line, then Console.WriteLine() in SystemEvents_SessionSwitch() will work
Console.ReadKey();
}
static void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
Console.WriteLine("SessionSwitch"); //this does not working
System.Diagnostics.Debug.WriteLine("SessionSwitchDebug"); //and this does not working too..
}
}
}
Updated:
The handler itself SystemEvents_SessionSwitch () is called. I specifically checked this by putting a breakpoint. And after locking computer breakpoint activated. But Console.WriteLine() does not output any text to console...
I can tell from the description of the problem that you are using .NET 4.5. Your Console.ReadKey() method takes a lock, new behavior in 4.5, it prevents other threads from writing to the console and mess up the display.
That lock prevents your event handler from writing to the console. It runs on another thread, necessary because you are using a console mode program that doesn't pump a message loop. The SystemEvents class will create its own to ensure the events fire. The upvoted answer is wrong about that.
What it tries to write does eventually make it to the console, but that of course happens a millisecond before the console window closes so you never see it.
This new 4.5 behavior does spell doom to quicky programs to test something. You'll need a better way to decide that your program is done, the "Hit any key to continue" method doesn't work so well anymore. A rather lame alternative that doesn't take the lock could be:
while (!Console.KeyAvailable) System.Threading.Thread.Sleep(100);
UPDATE: this problem was fixed in a .NET 4.5 update delivered through Windows Update. Not exactly sure when I got the update, somewhere around August 2013.
The event is simply not called. In your check the documentation, it says:
This event is only raised if the message pump is running. In a Windows
service, unless a hidden form is used or the message pump has been
started manually, this event will not be raised.
I'm currently having a problem, which seems to be related to closing a Form, while a scale, which is connected through a Serial Connection keeps sending data (about 3 packages per sek).
I handle new data over the DataReceived-Event (handling itself might be uninteresting for this issue, since I'm just matching data) Keep an eye on the COM_InUse variable and the allowFireDataReceived check.):
private void COMScale_DataReceived(object sender, EventArgs e)
{
if (allowFireDataReceived)
{
//set atomar state
COM_InUse = true;
//new scale:
if (Properties.Settings.Default.ScaleId == 1)
{
strLine = COMScale.ReadTo(((char)0x2).ToString());
//new scale:
Regex reg = new Regex(Constants.regexScale2);
Match m = reg.Match(strLine);
if (m.Success)
{
strGewicht = m.Groups[1].Value + m.Groups[2];
double dblComWeight;
double.TryParse(strGewicht, out dblComWeight);
dblScaleActiveWeight = dblComWeight / 10000;
//add comma separator and remove zeros
strGewicht = strGewicht.Substring(0, 1) + strGewicht.Substring(1, 2).TrimStart('0') + strGewicht.Substring(3);
strGewicht = strGewicht.Insert(strGewicht.Length - 4, ",");
//write to textbox
ThreadSafeSetActiveScaleText(strGewicht);
COMScale.DiscardInBuffer();
//MessageBox.Show(dblScaleActiveWeight.ToString(), "dblScaleActiveWeight");
}
}
//free atomar state
COM_InUse = false;
}
}
The COM_InUse variable is a global bool and "tells" if there is a current process of handling data.
The allowFireDataReceived is also a global bool and if set to false will lead to no extra handling of the data which has been sended.
My problem now is the following:
It seems that Eventhandling is a separate Thread, which leads to a deadlock on klicking the Cancel-Button since the COM_InUse will never turn to false, even if the Event was handled (see end of COMScale_DataReceived, where COM_InUse is set to false).
While setting allowFireDataReceived = false works perfectly (no handling any more), as I said: the while loop will not terminate.
private void bScaleCancel_Click(object sender, EventArgs e)
{
allowFireDataReceived = false;
while (COM_InUse)
{
;
}
if (!COM_InUse)
{
ret = 1;
SaveClose();
}
}
When I comment out the while-block I have to click twice on the button, but it works without a crash. Since this very user unfriendly, I'm searching for an alternative way to safely close the window.
Info:
Simply closing (without checking if the COM-Data was processed) lead to a fatal crash.
So, maybe someone can explain to me what exactly causes this problem or can provide a solution to this. (Maybe one would be to trigger the Cancel-Clicking Event again, but that is very ugly)
Greetings!
I count on you :)
//edit:
Here is the current code of
private void ThreadSafeSetActiveScaleText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.lScaleActive.InvokeRequired)
{
SafeActiveScaleTextCallback d = new SafeActiveScaleTextCallback(ThreadSafeSetActiveScaleText);
this.Invoke(d, new object[] { text });
}
else
{
this.lScaleActive.Text = text;
}
}
ThreadSafeSetActiveScaleText(strGewicht);
Yes, the DataReceived event runs on a threadpool thread. You already knew that, you wouldn't have called it "ThreadSafe" otherwise. What we can't see is what is inside this method. But given the outcome, it is highly likely that you are using Control.Invoke().
Which is going to cause deadlock when you loop on COM_InUse in code that runs on the UI thread. The Control.Invoke() method can only complete when the UI thread has executed the delegate target method. But the UI thread can only do that when it is idle, pumping the message loop and waiting for Windows messages. And invoke requests. It cannot do this while it looping inside the Click event handler. So Invoke() cannot complete. Which leaves the COM_InUse variable for ever set to true. Which leaves the Click event handler forever looping. Deadlock city.
The exact same problem occurs when you call the SerialPort.Close() method, the port can only be closed when all events have been processed.
You will need to fix this by using Control.BeginInvoke() instead. Make sure the data is still valid by the time the delegate target starts executing. Pass it as an argument for example, copying if necessary.
Closing the form while the scale is unrelentingly sending data is in general a problem. You'll get an exception when you invoke on a disposed form. To fix this, you'll need to implement the FormClosing event handler and set e.Cancel to true. And unsubscribe the DataReceived event and start a timer. Make the Interval a couple of seconds. When the timer Ticks, you can close the form again, now being sure that all data was drained and no more invokes can occur.
Also note that calling DiscardInBuffer() is only good to randomly lose data.