C# ISynchronizeInvoke Question - c#

At the risk of sounding like a total noob, how do I implement ISynchronizeInvoke on a System.Timers.Timer?
I have a class (no UI) that is making calls to mciSendString. I have a timer that is supposed to poll for the current status. All of the calls from the class work, but not the ones coming from the timers elapsed event. I've tracked it down to being on a different thread, but I've not gotten any further than that. I think I need to invoke a delegate on the same thread as the class, but I haven't had any luck accomplishing it yet.
Code Sample:
[DllImport("winmm.dll")]
private static extern Int32 mciSendString(string command, StringBuilder buffer, Int32 bufferSize, IntPtr hwndCallback);
public cPlayer()
{
tmrPoll = new System.Timers.Timer(1000);
tmrPoll.Enabled = false;
tmrPoll.Elapsed += new ElapsedEventHandler(tmrPoll_tick);
}
public void tmrPoll_tick(object source, ElapsedEventArgs e)
{
Poll();
}
private void Poll()
{
Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
tmrPoll.Stop();
int res = 0;
res = mciSendString("status MediaFile position", _sbBuffer, _sbBuffer.Capacity, IntPtr.Zero);
if (res == 0) _position = int.Parse(_sbBuffer.ToString());
if (res == 0)
{
Console.WriteLine("Position = " + _sbBuffer.ToString());
} else {
Console.WriteLine("Failed: Error " + res.ToString());
}
res = mciSendString("status MediaFile length", _sbBuffer, _sbBuffer.Capacity, IntPtr.Zero);
if (res == 0) Console.WriteLine("Length = " + _sbBuffer.ToString());
if (res == 0) _length = int.Parse(_sbBuffer.ToString());
res = mciSendString("status MediaFile mode", _sbBuffer, _sbBuffer.Capacity, IntPtr.Zero);
if (res == 0) Console.WriteLine("Mode = " + _sbBuffer.ToString());
}
private void SendCommand(string cmd)
{
mciSendString(cmd, null, 0, IntPtr.Zero);
Poll();
}
For clarification, if I call from SendCommand (whatever it may be, Play, Stop, etc.) it works and the result of Poll() is what I'd expect. When the timer fires, the result (res) is 263 which is MCIERR_INVALID_DEVICE_NAME. The threadID on the failed call is different from the one that succeeds, that's why I figured I needed to use ISynchronizeInvoke.

I got an answer over at the msdn forum. Someone left me a message with a link to codeplex (nito.async). I used the GenericSynchronizingObject to get the job done.

What is the error code that you are getting? I imagine part of it has to do with the hwndCallback parameter, but without seeing any code samples, it's hard to tell.
However, you don't need to implement ISynchronizeInvoke here. You might have to call it if you are passing a window handle for the callback. In that case, you will need to call the Invoke method on the managed representation that implements ISynchronizeInvoke. In other words, the control/form that you got the handle from.

Related

Function works in .NET framework 2.0 but not in any other version

On .NET 5 (and .NET core 3 and 3.1) when debugging after around 5 seconds the code throws System.ExecutionEngineException which seems like it should never pop up as it is something obsolete as far as I understood from searching.
The same code on .NET Framework >2 (e.g 4.8 or 4.7.2) similarly works for around 5 seconds then throws the following exception:
Managed Debugging Assistant 'CallbackOnCollectedDelegate' : 'A callback was made on a garbage collected delegate of type
'SoundCheck!SoundCheck.CHCNetSDK+VOICEDATACALLBACKV30::Invoke'. This
may cause application crashes, corruption and data loss. When passing
delegates to unmanaged code, they must be kept alive by the managed
application until it is guaranteed that they will never be called.'
But on .NET Framework 2 it works magically without any issues.
As far as I understand I should somehow make it so that garbage collection stops and does not collect this method. But I am unfamiliar with this. I do not know how I should approach this.
I am using the Hikvision SDK https://www.hikvision.com/en/support/download/sdk/
The code:
Dll import:
public delegate void VOICEDATACALLBACKV30(int lVoiceComHandle, IntPtr pRecvDataBuffer, uint dwBufSize, byte byAudioFlag, System.IntPtr pUser);
[DllImport(#"..\bin\HCNetSDK.dll")]
public static extern int NET_DVR_StartVoiceCom_V30(int lUserID, uint dwVoiceChan, bool bNeedCBNoEncData, VOICEDATACALLBACKV30 fVoiceDataCallBack, IntPtr pUser);
Start button:
private void btnVioceTalk_Click(object sender, EventArgs e)
{
if (m_bTalk == false)
{
CHCNetSDK.VOICEDATACALLBACKV30 VoiceData = new CHCNetSDK.VOICEDATACALLBACKV30(VoiceDataCallBack);
lVoiceComHandle = CHCNetSDK.NET_DVR_StartVoiceCom_V30(m_lUserID, 1, true, VoiceData, IntPtr.Zero);
if (lVoiceComHandle < 0)
{
iLastErr = CHCNetSDK.NET_DVR_GetLastError();
str = "NET_DVR_StartVoiceCom_V30 failed, error code= " + iLastErr;
MessageBox.Show(str);
return;
}
else
{
btnVioceTalk.Text = "Stop Talk";
m_bTalk = true;
}
}
else
{
if (!CHCNetSDK.NET_DVR_StopVoiceCom(lVoiceComHandle))
{
iLastErr = CHCNetSDK.NET_DVR_GetLastError();
str = "NET_DVR_StopVoiceCom failed, error code= " + iLastErr;
MessageBox.Show(str);
return;
}
else
{
btnVioceTalk.Text = "Start Talk";
m_bTalk = false;
}
}
}
Callback function:
public void VoiceDataCallBack(int lVoiceComHandle, IntPtr pRecvDataBuffer, uint dwBufSize, byte byAudioFlag, System.IntPtr pUser)
{
byte[] sString = new byte[dwBufSize];
Marshal.Copy(pRecvDataBuffer, sString, 0, (Int32)dwBufSize);
if (byAudioFlag ==0)
{
string str = "sound1.pcm";
FileStream fs = new FileStream(str, FileMode.Create);
int iLen = (int)dwBufSize;
fs.Write(sString, 0, iLen);
fs.Close();
}
if (byAudioFlag == 1)
{
string str = "sound2.pcm";
FileStream fs = new FileStream(str, FileMode.Create);
int iLen = (int)dwBufSize;
fs.Write(sString, 0, iLen);
fs.Close();
}
}
From what I understood you declare a delegate type VOICEDATACALLBACKV30
and you have a method implementing that signature. This is method VoiceDataCallBack
In the line CHCNetSDK.VOICEDATACALLBACKV30 VoiceData = new CHCNetSDK.VOICEDATACALLBACKV30(VoiceDataCallBack);
you instantiate that callback inside an eventhandler, so it's managed memory allocation "lives" on the stack, for a short time.
Then you pass it to some SDK function.
Seems the unmanaged SDK functions keeps on working with that delegate, for an obvoiusy longer time and hold an unmanaged reference/pointer to that delegate .
But your .NET code has already garbage collected it after a short while, because it is no longer referenced in managed code.
So when the SDK invokes the callback the first time after its managed memory has been collected, it's crashing.
So need to keep a reference in managed code, by simply assigning it to a field, in order to keep it alive on the heap. So it is not GC'ed.
//declare field
private CHCNetSDK.VOICEDATACALLBACKV30 _voiceData;
....
//inside 'btnVioceTalk_Click' event handler
if (_voiceData == null) {
_voiceData = new CHCNetSDK.VOICEDATACALLBACKV30(VoiceDataCallBack);
}
Likely .NET Framework 2 had another GC implementation/algorithm.

The C# callback function is not called from C++ code

I need to communicate with a program from my C# code. I have received a communication library from the company that made the program, along with a quite detailed explanation on how to integrate with the application. I feel I have done everything correctly, the C++ functions all return success statusses, but the most important part, the callback function, is never invoked.
I cannot use [DllImport(...)], I have to use LoadLibrary, GetProcAddress and Marshal.GetDelegateForFunctionPointer to load the library into memory and call it's functions. This is so I keep an instance of the library in memory, so it remembers the callback function. This is because the callback function is registered and then used by subsequent requests to communicate additional information.
I'm keeping a reference to the callback function so the GC doesn't collect it and it isn't available for the C++ code.
When I wait for the callback, nothing happens. I don't get errors, I don't see anything specific happening, it just lets me wait forever. The callback doesn't happen.
I'm hoping that somebody can tell me what I have missed, because I don't see the solution anymore.
class Program
{
private static Callback keepAlive;
static void Main(string[] args)
{
try
{
var comLib = LoadLibrary(#"C:\BIN\ComLib.dll");
var isInstalledHandle = GetProcAddress(comLib, nameof(IsInstalled));
var isInstalled = Marshal.GetDelegateForFunctionPointer<IsInstalled>(isInstalledHandle);
var isInstalledResult = isInstalled("activation-key"); // returns success
Console.WriteLine("Is installed result: " + isInstalledResult);
var registerCallbackHandle = GetProcAddress(comLib, nameof(RegisterCallback));
var registerCallback = Marshal.GetDelegateForFunctionPointer<RegisterCallback>(registerCallbackHandle);
keepAlive = CallbackFunc;
var registerResult = registerCallback(keepAlive); // returns success
Console.WriteLine("Register result: " + registerResult);
var initHandle = GetProcAddress(comLib, nameof(Init));
var init = Marshal.GetDelegateForFunctionPointer<Init>(initHandle);
var initResult = init("ERP_INTEGRATION", "activation-key"); // returns success
Console.WriteLine("Init result: " + initResult);
Console.WriteLine("Wait...");
Console.ReadLine();
FreeLibrary(comLib);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static int CallbackFunc(int id, string data)
{
Console.WriteLine($"{id} > {data}");
return 0;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int IsInstalled(string registryKey);
private delegate int Callback(int id, string data);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int RegisterCallback(Callback callback);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int Init(string id, string command);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
}
EDIT 1: I got back from the developers of the software. Apparently, because I'm doing all the work on the main thread, the app can't call back because the thread is busy. I should offload the app (and the callback method) to another thread so the app is free to call the callback funcion. Unfortunately, I have no idea how to do this.
EDIT 2: On request, I put the code in a WinForms app and the callback works nicely there. I do not fully understand why, except that the callback must happen while a) the main thread is free (waiting for input from the form) or b) it offloads the callback to another thread. I have no idea how to simulate either in a console app.
I found an answer that is a little hacky, but it works. I converted my console application to WinForms application. This gives me access to System.Windows.Forms.Application.DoEvents. That allows me to start up a Task which runs out of process and keeps refreshing the events that need to be handled. This allows the external program to invoke the callback function.
private bool _callbackOccured = false;
static async Task Main(string[] args)
{
try
{
var comLib = LoadLibrary(#"C:\BIN\ComLib.dll");
await Task.Run(async () => {
var isInstalledHandle = GetProcAddress(comLib, nameof(IsInstalled));
var isInstalled = Marshal.GetDelegateForFunctionPointer<IsInstalled>(isInstalledHandle);
var isInstalledResult = isInstalled("activation-key"); // returns success
Console.WriteLine("Is installed result: " + isInstalledResult);
var registerCallbackHandle = GetProcAddress(comLib, nameof(RegisterCallback));
var registerCallback = Marshal.GetDelegateForFunctionPointer<RegisterCallback>(registerCallbackHandle);
keepAlive = CallbackFunc;
var registerResult = registerCallback(keepAlive); // returns success
Console.WriteLine("Register result: " + registerResult);
var initHandle = GetProcAddress(comLib, nameof(Init));
var init = Marshal.GetDelegateForFunctionPointer<Init>(initHandle);
var initResult = init("ERP_INTEGRATION", "activation-key"); // returns success
Console.WriteLine("Init result: " + initResult);
while (!_callbackOccured)
{
await Task.Delay(100);
Appliction.DoEvents();
}
FreeLibrary(comLib);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private static int CallbackFunc(int id, string data)
{
Console.WriteLine($"{id} > {data}");
_callbackOccured = true;
return 0;
}
Edit: it didn't fully work with Task.Run, I had to use an actual Thread.Start to get it to work correctly. A Task got killed too quickly and I still had the same problem that the external app was closed before it got to do anything usefull, but with a Thread it works as expected.
see answer to this: C# C++ Interop callback. Try converting your delegate to a native function pointer before you pass it to the library.

invoke with multiple arguments resulting in FormatException

I'm trying to invoke a Form method from a different thread. In the form class, I have:
delegate int ReplaceMessageCallback(string msg, int key);
public int ReplaceMessage(string msg, int key)
{
if (this.InvokeRequired)
{
ReplaceMessageCallback amc = new ReplaceMessageCallback(ReplaceMessage);
object[] o = new object[] { msg, key };
return (int)this.Invoke(amc, o);
}
bool found = false;
int rv;
lock (this)
{
if (key != 0)
{
found = RemoveMessage(key);
}
if (found)
{
rv = AddMessage(msg, key);
}
else
{
rv = AddMessage(msg);
}
}
MainForm.EventLogInstance.WriteEntry((found)
? EventLogEntryType.Information
: EventLogEntryType.Warning,
IntEventLogIdent.MessageFormReplace1,
String.Format("MessageForm::ReplaceMessage(({2},{0}) returns {1}.\n\n(The message {3} exist to be replaced.)",
key,
rv,
msg,
(found)
? "did"
: "did not"));
return rv;
}
When I run this, I get an exception "FormatException was unhandled" "Index (zero based) must be greater than or equal to zero and less than the size of the argument list." on the call to Invoke.
Essentially this same code fragment works fine on class methods that only take a single parameter, so I assume I'm doing something wrong with the object array but I have no idea what.
An easier way to handle this is:
if (this.InvokeRequired)
{
int rslt;
this.Invoke((MethodInvoker) delegate
{
rslt = ReplaceMessage(msg, key);
}
return rslt;
}
It turns out that the invoke call will pass along exceptions within the function it calls, and you can't step (F11 in debugger) into it. I assumed that it would step into the called code, so when it failed I thought it was the actual Invoke call.
I messed up a String.Format in the body of the function, and Invoke passed that exception to me with no indication of where in the code the problem actually happened.

Workaround to add a default parameterless constructor to a struct

Let me describe my problem - I have a struct that wraps an unmanaged handle (let's call it Mem). I need this handle to call a particular method (say "retain" or alternatively, maintain a reference count) whenever it is copied.
In other words, I need a struct that maintains a reference count internally (I have a mechanism externally as well, but need a way to invoke that mechanism).
Unfortunately, C# doesn't let me do this in any way.
I also cannot make Mem a class because I will pass an array of these structs to unmanaged code and I do NOT want to convert them one by one before passing them in (just pin and pass).
Does anyone know of any workaround (IL Weaving, etc) that can be applied to add this behavior in? I believe IL doesn't prevent me from doing this, only C#, correct?
I am happy to answer any questions about the framework and restrictions I have, but I am not looking for - "please change your design" or "don't use C# for this" answers, thanks very much.
I believe IL doesn't prevent me from doing this, only C#, correct?
Yes, that's right where "this" is "a parameterless constructor for a struct". I blogged about that a while ago.
However, having a parameterless constructor does not do what you want in terms of notifying you every time a struct is copied. There's basically no way of doing that, as far as I'm aware. The constructor isn't even called in every case when you end up with a "default" value, and even if it were, it's certainly not called just for copy operations.
I know you don't want to hear "please change your design" but you're simply asking for something which does not exist in .NET.
I would suggest having some sort of method on the value type which returns a new copy, having taken appropriate action. You then need to make sure you always call that method at the right time. There will be nothing preventing you from getting this wrong, other than whatever testing you can build.
Does anyone know of any workaround (IL Weaving, etc) that can be applied to add this behavior in? I believe IL doesn't prevent me from doing this, only C#, correct?
This is correct, somewhat. The reason C# prevents this is that the constructor will not be used, even if its defined in IL, in many cases. Your case is one of these - if you create an array of structs, the constructors will not be called, even if they're defined in the IL.
Unfortunately, there isn't really a workaround since the CLR won't call the constructors, even if they exist.
Edit: I hosted the work from this answer on GitHub: the NOpenCL library.
Based on your comment, I determined the following as an appropriate long-term course of action for the problems being discussed here. Apparently the problem centers around the use of OpenCL within managed code. What you need is a proper interop layer for this API.
As an experiment, I wrote a managed wrapper for a large portion of the OpenCL API to evaluate the viability of SafeHandle to wrap cl_mem, cl_event, and other objects which require a call to clRelease* for cleanup. The most challenging part was implementing methods like clEnqueueReadBuffer which can take an array of these handles as a parameter. The initial declaration of this method looked like the following.
[DllImport(ExternDll.OpenCL)]
private static extern ErrorCode clEnqueueReadBuffer(
CommandQueueSafeHandle commandQueue,
BufferSafeHandle buffer,
[MarshalAs(UnmanagedType.Bool)] bool blockingRead,
IntPtr offset,
IntPtr size,
IntPtr destination,
uint numEventsInWaitList,
[In, MarshalAs(UnmanagedType.LPArray)] EventSafeHandle[] eventWaitList,
out EventSafeHandle #event);
Unfortunately, the P/Invoke layer does not support marshaling an array of SafeHandle objects, so I implemented an ICustomMarshaler called SafeHandleArrayMarshaler to handle this. Note that the current implementation does not use Constrained Execution Regions, so an asynchronous exception during marshaling can cause it to leak memory.
internal sealed class SafeHandleArrayMarshaler : ICustomMarshaler
{
private static readonly SafeHandleArrayMarshaler Instance = new SafeHandleArrayMarshaler();
private SafeHandleArrayMarshaler()
{
}
public static ICustomMarshaler GetInstance(string cookie)
{
return Instance;
}
public void CleanUpManagedData(object ManagedObj)
{
throw new NotSupportedException();
}
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return;
GCHandle managedHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(pNativeData, -IntPtr.Size));
SafeHandle[] array = (SafeHandle[])managedHandle.Target;
managedHandle.Free();
for (int i = 0; i < array.Length; i++)
{
SafeHandle current = array[i];
if (current == null)
continue;
if (Marshal.ReadIntPtr(pNativeData, i * IntPtr.Size) != IntPtr.Zero)
array[i].DangerousRelease();
}
Marshal.FreeHGlobal(pNativeData - IntPtr.Size);
}
public int GetNativeDataSize()
{
return IntPtr.Size;
}
public IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj == null)
return IntPtr.Zero;
SafeHandle[] array = (SafeHandle[])ManagedObj;
int i = 0;
bool success = false;
try
{
for (i = 0; i < array.Length; success = false, i++)
{
SafeHandle current = array[i];
if (current != null && !current.IsClosed && !current.IsInvalid)
current.DangerousAddRef(ref success);
}
IntPtr result = Marshal.AllocHGlobal(array.Length * IntPtr.Size);
Marshal.WriteIntPtr(result, 0, GCHandle.ToIntPtr(GCHandle.Alloc(array, GCHandleType.Normal)));
for (int j = 0; j < array.Length; j++)
{
SafeHandle current = array[j];
if (current == null || current.IsClosed || current.IsInvalid)
{
// the memory for this element was initialized to null by AllocHGlobal
continue;
}
Marshal.WriteIntPtr(result, (j + 1) * IntPtr.Size, current.DangerousGetHandle());
}
return result + IntPtr.Size;
}
catch
{
int total = success ? i + 1 : i;
for (int j = 0; j < total; j++)
{
SafeHandle current = array[j];
if (current != null)
current.DangerousRelease();
}
throw;
}
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
throw new NotSupportedException();
}
}
This allowed me to successfully use the following interop declaration.
[DllImport(ExternDll.OpenCL)]
private static extern ErrorCode clEnqueueReadBuffer(
CommandQueueSafeHandle commandQueue,
BufferSafeHandle buffer,
[MarshalAs(UnmanagedType.Bool)] bool blockingRead,
IntPtr offset,
IntPtr size,
IntPtr destination,
uint numEventsInWaitList,
[In, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(SafeHandleArrayMarshaler))] EventSafeHandle[] eventWaitList,
out EventSafeHandle #event);
This method is declared as private so I could expose it through a method that handles the numEventsInWaitList and eventWaitList arguments properly according to the OpenCL 1.2 API documentation.
internal static EventSafeHandle EnqueueReadBuffer(CommandQueueSafeHandle commandQueue, BufferSafeHandle buffer, bool blocking, IntPtr offset, IntPtr size, IntPtr destination, EventSafeHandle[] eventWaitList)
{
if (commandQueue == null)
throw new ArgumentNullException("commandQueue");
if (buffer == null)
throw new ArgumentNullException("buffer");
if (destination == IntPtr.Zero)
throw new ArgumentNullException("destination");
EventSafeHandle result;
ErrorHandler.ThrowOnFailure(clEnqueueReadBuffer(commandQueue, buffer, blocking, offset, size, destination, eventWaitList != null ? (uint)eventWaitList.Length : 0, eventWaitList != null && eventWaitList.Length > 0 ? eventWaitList : null, out result));
return result;
}
The API is finally exposed to user code as the following instance method in my ContextQueue class.
public Event EnqueueReadBuffer(Buffer buffer, bool blocking, long offset, long size, IntPtr destination, params Event[] eventWaitList)
{
EventSafeHandle[] eventHandles = null;
if (eventWaitList != null)
eventHandles = Array.ConvertAll(eventWaitList, #event => #event.Handle);
EventSafeHandle handle = UnsafeNativeMethods.EnqueueReadBuffer(this.Handle, buffer.Handle, blocking, (IntPtr)offset, (IntPtr)size, destination, eventHandles);
return new Event(handle);
}

Render Usercontrol while AutoResetEvent.WaitOne()

maybe it is a bad question, but I have a Client App which sends some Commands to the server. I made a little function for sending commands, which simply waits until a message is received over AutoResetEvent.WaitOne() and then returns the message as string. I used this function very often in my client app, because it was a fast way to implement sth.
Now I want to show a LoadingCircle in this app while it is waiting from the response from the Server. I already wrote sth for that in the message receiving function, which simply adds the control to the Form. This is working find, but UserControl(Loading Circle), which redraws after some milliseconds, doesnt redraw anymore, because AutoResetEvent.WaitOne() is blocking the GUI Thread. I know that I could move the message receiving part into another Thread, but I dont want to change the concept of this function, because I used the message receiving function over 150x.
So my question is: Is there a lazy way to do that, so that It does GUI Events while its waiting, or maybe I can move the drawing part of the LoadingCircle Control into another Thread?
Any help will be greatly appreciated
You have essentially taken an async operation and turned it into a synchronous operation via AutoResetEvent.WaitOne. And now you want to turn it back into an async operation.
My suggestion would be to separate the function from the UI. Then tool a synchronous and an async option. That way you get to keep a fully functioning app and can incrementally release during your code re-write of the 150 instances that need changed.
The new async and await keywords in 4.5 should serve you well here.
I found a small fix for that here, which works well(I cant take credit for it)
private static TimeSpan InfiniteTimeout = TimeSpan.FromMilliseconds(-1);
private const Int32 MAX_WAIT = 100;
public static bool Wait(WaitHandle handle, TimeSpan timeout)
{
Int32 expireTicks;
bool signaled;
Int32 waitTime;
bool exitLoop;
// guard the inputs
if (handle == null) {
throw new ArgumentNullException("handle");
}
else if ((handle.SafeWaitHandle.IsClosed)) {
throw new ArgumentException("closed wait handle", "handle");
}
else if ((handle.SafeWaitHandle.IsInvalid)) {
throw new ArgumentException("invalid wait handle", "handle");
}
else if ((timeout < InfiniteTimeout)) {
throw new ArgumentException("invalid timeout <-1", "timeout");
}
// wait for the signal
expireTicks = (int)Environment.TickCount + timeout.TotalMilliseconds;
do {
if (timeout.Equals(InfiniteTimeout)) {
waitTime = MAX_WAIT;
}
else {
waitTime = (expireTicks - Environment.TickCount);
if (waitTime <= 0) {
exitLoop = true;
waitTime = 0;
}
else if (waitTime > MAX_WAIT) {
waitTime = MAX_WAIT;
}
}
if ((handle.SafeWaitHandle.IsClosed)) {
exitLoop = true;
}
else if (handle.WaitOne(waitTime, false)) {
exitLoop = true;
signaled = true;
}
else {
if (Application.MessageLoop) {
Application.DoEvents();
}
else {
Thread.Sleep(1);
}
}
}
while (!exitLoop);
return signaled;
}

Categories

Resources