从C#进行P / Invoke调用时,异步过程调用如何处理已封送的委托?
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了从C#进行P / Invoke调用时,异步过程调用如何处理已封送的委托?,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含12268字,纯文字阅读大概需要18分钟。
内容图文
![从C#进行P / Invoke调用时,异步过程调用如何处理已封送的委托?](/upload/InfoBanner/zyjiaocheng/678/10f5c98ab82d48d79ce2d43cd8b5b4c8.jpg)
我想知道,当您在以下我的特殊情况下,通过P / Invoke将回调委托编组到DLL时,是否有可能成为本地世界中托管线程管理问题的受害者?
This MSDN article on Managed and Unmanaged Threading in Windows状态:
“操作系统ThreadId与托管线程没有固定的关系,因为非托管主机可以控制托管线程和非托管线程之间的关系.特别是,复杂的主机可以使用Fiber API针对同一操作系统线程调度许多托管线程. ,或在不同的操作系统线程之间移动托管线程.”
首先,本文介绍谁是非托管主机?如果您像下面的示例代码中那样使用封送处理,那么那里的非托管主机是谁或什么?
同样,this StackOverflow question的可接受答案指出:
“从CLR的角度来看,一个托管线程在其生命周期中由多个不同的本机线程支持是完全合法的.这意味着GetCurrentThreadId的结果可以(并且将)在整个线程生命周期中发生变化.”
那么,这是否意味着我的APC将在本机线程中排队,或者由于封送处理层而直接委派给我的托管线程?
这是例子.假设我使用以下类在托管代码中P /调用NotifyServiceStatusChange函数来检查服务何时停止:
using System;
using System.Runtime.InteropServices;
namespace ServiceStatusChecking
{
class QueryServiceStatus
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
public static void WaitForServiceToStop(string serviceName)
{
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
GCHandle notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(uint.MaxValue, true);
notifyHandle.Free();
CloseServiceHandle(hService);
}
CloseServiceHandle(hSCM);
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
是将APC排队到托管我的托管线程的任何本机线程上,还是将APC直接委派给我的托管线程?我以为委托在那里确实可以处理这种情况,因此我们不必担心本地如何管理托管线程,但是我可能错了!
编辑:我想这是一个更令人满意的答案.
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServiceAssistant
{
class ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
{
Thread.BeginThreadAffinity();
GCHandle? notifyHandle = null;
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
IntPtr hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = ((GCHandle)notifyHandle).AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(timeout, true);
CloseServiceHandle(hService);
}
CloseServiceHandle(hSCM);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != null)
{
((GCHandle)notifyHandle).Free();
}
Thread.EndThreadAffinity();
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
重新编辑!我想这是一个甚至更好的答案:
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace ServiceAssistant
{
class ServiceHelper
{
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public class SERVICE_NOTIFY
{
public uint dwVersion;
public IntPtr pfnNotifyCallback;
public IntPtr pContext;
public uint dwNotificationStatus;
public SERVICE_STATUS_PROCESS ServiceStatus;
public uint dwNotificationTriggered;
public IntPtr pszServiceNames;
};
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct SERVICE_STATUS_PROCESS
{
public uint dwServiceType;
public uint dwCurrentState;
public uint dwControlsAccepted;
public uint dwWin32ExitCode;
public uint dwServiceSpecificExitCode;
public uint dwCheckPoint;
public uint dwWaitHint;
public uint dwProcessId;
public uint dwServiceFlags;
};
[DllImport("advapi32.dll")]
static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);
[DllImport("advapi32.dll")]
static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);
[DllImport("advapi32.dll")]
static extern uint NotifyServiceStatusChange(IntPtr hService, uint dwNotifyMask, IntPtr pNotifyBuffer);
[DllImportAttribute("kernel32.dll")]
static extern uint SleepEx(uint dwMilliseconds, bool bAlertable);
[DllImport("advapi32.dll")]
static extern bool CloseServiceHandle(IntPtr hSCObject);
delegate void StatusChangedCallbackDelegate(IntPtr parameter);
/// <summary>
/// Block until a service stops, is killed, or is found to be already dead.
/// </summary>
/// <param name="serviceName">The name of the service you would like to wait for.</param>
/// <param name="timeout">An amount of time you would like to wait for. uint.MaxValue is the default, and it will force this thread to wait indefinitely.</param>
public static void WaitForServiceToStop(string serviceName, uint timeout = uint.MaxValue)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle notifyHandle = default(GCHandle);
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
SERVICE_NOTIFY notify = new SERVICE_NOTIFY();
notify.dwVersion = 2;
notify.pfnNotifyCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
notify.ServiceStatus = new SERVICE_STATUS_PROCESS();
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
IntPtr pinnedNotifyStructure = notifyHandle.AddrOfPinnedObject();
NotifyServiceStatusChange(hService, (uint)0x00000001, pinnedNotifyStructure);
SleepEx(timeout, true);
}
}
}
finally
{
// Clean up at the end of our operation, or if this thread is aborted.
if (hService != IntPtr.Zero)
{
CloseServiceHandle(hService);
}
if (hSCM != IntPtr.Zero)
{
CloseServiceHandle(hSCM);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != default(GCHandle))
{
notifyHandle.Free();
}
Thread.EndThreadAffinity();
}
}
public static void ReceivedStatusChangedEvent(IntPtr parameter)
{
}
}
}
解决方法:
是的,有可能成为这些问题的受害者.在这种特殊情况下,很难.当本机框架位于托管线程的堆栈上时,主机无法将托管线程切换到其他OS线程,并且由于您立即p / invoke SleepEx,因此主机用于切换托管线程的窗口在两个p /调用.但是,当您需要在同一个OS线程上进行p /调用并且存在Thread.BeginThreadAffinity()来覆盖这种情况时,有时还是不太可能的.
现在是关于APC的问题.请记住,操作系统对托管线程一无所知.因此,当APC执行可警报的操作时,它将被传递到原始本机线程中.在这种情况下,我不知道CLR主机如何创建托管上下文,但是如果托管线程与OS线程是一对一的,则回调可能会使用托管线程的上下文.
更新您的新代码现在更加安全,但是您在另一个方向上走得太远了:
1)不需要使用线程相似性包装整个代码.仅包装确实需要在同一OS线程上运行的两个p /调用(通知和睡眠).不管是否使用有限的超时都没有关系,因为线程亲和性要解决的问题是两个p /调用之间从托管到OS线程的迁移.回调不应该假设它无论如何都在任何特定的托管线程上运行,因为它几乎可以安全地执行,而应该执行的很少:互锁的操作,设置事件和完成TaskCompletionSources就是这样.
2)GCHandle是一个简单的IntPtr大小的结构,可以比较相等性.代替使用GCHandle ?,而是使用普通的GCHandle并与default(GCHandle)进行比较.此外,GCHandle?在一般原则上对我来说似乎有些可疑.
3)当您关闭服务手柄时,通知将停止. SCM手柄可以保持打开状态,您可能需要保留它以便下次检查.
// Thread.BeginThreadAffinity();
// GCHandle? notifyHandle = null;
var hSCM = OpenSCManager(null, null, (uint)0xF003F);
if (hSCM != IntPtr.Zero)
{
StatusChangedCallbackDelegate changeDelegate = ReceivedStatusChangedEvent;
var notifyHandle = default(GCHandle);
var hService = OpenService(hSCM, serviceName, (uint)0xF003F);
if (hService != IntPtr.Zero)
{
...
notifyHandle = GCHandle.Alloc(notify, GCHandleType.Pinned);
var addr = notifyHandle.AddrOfPinnedObject();
Thread.BeginThreadAffinity();
NotifyServiceStatusChange(hService, (uint)0x00000001, addr);
SleepEx(timeout, true);
Thread.EndThreadAffinity();
CloseServiceHandle(hService);
}
GC.KeepAlive(changeDelegate);
if (notifyHandle != default(GCHandle))
notifyHandle.Free();
CloseServiceHandle(hSCM);
}
另外,为了尽可能安全,如果代码将长时间运行,或者正在编写库,则必须使用受约束的区域和/或SafeHandles来确保即使线程为流产了.查看所有绕过的BCL代码,例如System.Threading.Mutex(使用Reflector或CLR源代码).至少,请使用SafeHandles并尝试/最后尝试释放GCHandle和结束线程关联.
至于与回调相关的问题,这些只是正常的多线程问题的一种坏情况:死锁,活动锁,优先级反转等.这种APC回调最糟糕的事情是(除非您自己阻塞整个线程,直到它会发生,在这种情况下,仅阻塞本机代码会更容易),您无需控制它的发生时间:您的线程可能位于BCL的深处,等待I / O,信号事件等,这是很难推断程序可能处于的状态.
内容总结
以上是互联网集市为您收集整理的从C#进行P / Invoke调用时,异步过程调用如何处理已封送的委托?全部内容,希望文章能够帮你解决从C#进行P / Invoke调用时,异步过程调用如何处理已封送的委托?所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。