从任意(同步)方法启动异步/等待



如果我有一个常规的方法调用,并且我需要使用 async/await 来"启动",那么启动它的最佳方法是什么? 无需详细介绍,我正在使用 Hangfire 来处理作业,并且由于我认为某些场景超出了问题的范围,Hangfire 作业是同步运行的,但随后我想稍后启动异步和等待,以便实际的"作业代码"可以根据需要/需要使用 async/await。 以下是启用异步和等待的最佳方法吗?

public void SynchMethod()
{
var inputPackage = XElement.Parse( "NormallyPassedIn" );
var hangfireJob = CreateJob( inputPackage );
Task.Run( async () =>
{
// This Execute method implementation wants to use await on several
// helper methods it calls, so this is how I thought to allow for that
await hangfireJob.Execute( inputPackage );
} ).GetAwaiter().GetResult();
}

更新2:尽可能高地阻止...

因此,为了尽可能高地阻止 Stephen 的建议(基本上是在第一次/唯一的跨域调用中),我试图将我的代码更改为以下内容:

var appDomain = AppDomain.CreateDomain( info.ApplicationName, AppDomain.CurrentDomain.Evidence, info );
...
instance = appDomain.CreateInstanceAndUnwrap( jobInvokerType.Assembly.FullName, jobInvokerType.FullName ) as JobInvoker;
...
instance.Process( this, inputPackage.ToString() ).GetAwaiter().GetResult();

具有以下处理功能:

public async Task Process( IHangfireJobContext jobContext, string inputPackageXml )
{
...
var hangfireJob = CreateJob( assembly, jobTypeName );
await hangfireJob.Execute( inputPackage, jobContext );
}

请记住,hangfireJob.Execute是我希望能够使用 async/await 的"真正"方法。 一旦hangfireJob.Execute使用await,就会抛出以下异常:

类型 'System.Threading.Tasks.Task'1[[System.Threading.Tasks.VoidTaskResult, mscorlib, 版本=4.0.0.0, 区域性=中性, PublicKeyToken=b77a5c561934e089]]' in Assembly 'mscorlib, 版本=4.0.0.0,区域性=中性,公钥令牌=b77a5c561934e089' 是 未标记为可序列化。

服务器堆栈跟踪:在 System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType 类型)在 System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context) at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo() 在 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) at System.Runtime.Serialization.Formatters.Binary.ObjectWriter.Serialize(Object graph, Header[] inHeaders, __BinaryWriter serWriter, Boolean fCheck)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers, Boolean fCheck)
at System.Runtime.Remoting.Channels.CrossAppDomainSerializer.SerializeMessageParts(ArrayList argsToSerialize) at System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage..ctor(IMethodReturnMessage MRM) 在 System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.SmuggleIfPossible(IMessage 味精)在 System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage&smuggledMrm) at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] 参数)

在 [0] 处重新引发异常:在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) at BTR.Evolution.Hangfire.JobInvoker.Process(IHangfireJobContext jobContext, String inputPackageXml) at BTR.Evolution.Hangfire.JobInvoker.Invoke(XElement inputPackage, PerformContext performContext, IJobCancelToken 取消令牌)在 C:\BTR\来源\进化\BTR.Evolution.Hangfire\JobInvoker.cs:line 86

所以我改回了:

public void Process( IHangfireJobContext jobContext, string inputPackageXml )

并将.GetAwaiter().GetResult()移到hangfireJob.Execute()的末尾:

hangfireJob.Execute( inputPackage, jobContext ).GetAwaiter().GetResult();

然后一切都奏效了。 将斯蒂芬的答案标记为正确。 不知道为什么我无法阻止第一个/唯一的跨域呼叫,但也许这是意料之中的。

更新 1:应用域创建/推理

所以我想我会根据下面的评论更新问题。 我真正运行的工作流程如下,我遇到的主要问题是我正在创建一个 AppDomain 并跨域调用。

  1. Hangfire 启动我的作业 (JobInvoker.Invoke) 运行(你可以让你的作业是同步或异步作业)。 所以最初,我试图像public async Task Invoke( XElement inputPackage, PerformContext performContext, IJobCancellationToken cancellationToken )一样异步运行。

  2. JobInvoker.Invoke通过var appDomain = AppDomain.CreateDomain( info.ApplicationName, AppDomain.CurrentDomain.Evidence, info );创建应用程序域

  3. JobInvoker.Invoke通过instance = appDomain.CreateInstanceAndUnwrap( jobInvokerType.Assembly.FullName, jobInvokerType.FullName )创建对象。

  4. 我尝试调用具有签名public async Task Process( IHangfireJobContext jobContext, string inputPackageXml )instance.Process方法。

  5. instance.Process通过反射创建了一个对象,其中包含上面的代码var hangfireJob = CreateJob()。 这个hangfireJob对象具有我需要在签名中async的方法。

  6. instance.Process通过await hangfireJob.Execute()称为hangfireJob。

  7. hangfireJob.Execute签名是public async Task Execute( XElement inputPackage, IHangfireJobContext jobContext )

然后代码看起来更好,因为我只需async我的所有方法上并随心所欲地使用await。 但是一旦hangfireJob.Execute尝试使用await并且我收到了以下异常(请记住,这是在单独的AppDomain中运行的,因此是异常):

类型 'System.Threading.Tasks.Task'1[[System.Threading.Tasks.VoidTaskResult, mscorlib, 版本=4.0.0.0, 区域性=中性, PublicKeyToken=b77a5c561934e089]]' in Assembly 'mscorlib, 版本=4.0.0.0,区域性=中性,公钥令牌=b77a5c561934e089' 是 未标记为可序列化。

这就是为什么我尝试从instance.Process内部"引入"异步编码(这基本上由我的原始SynchMethod表示),因为我的hangfireJob.Execute方法是真正需要对签名进行async的方法,以便我可以进行一些await调用。

鉴于这些信息,也许所有评论都不再完全适用? 如果您认为他们这样做,或者这是否像做await hangfireJob.Execute( inputPackage ).GetAwaiter().GetResult()一样简单(并摆脱Task.Run包装器),请告诉我。

在一般情况下,应强烈避免阻塞异步代码。

此规则的一个例外是控制台应用上的Main方法。

Win32 服务的一个有趣的怪癖是它们在体系结构上类似于控制台应用。 具体来说,它们有一个"主",在服务停止之前不应退出,并且它们没有提供SynchronizationContext。因此,在服务实现中阻止是合适的。

但是,我建议您遵循与控制台应用程序中的阻止相同的最佳实践:仅在堆栈的某个点进行阻止。

就细节而言,GetAwaiter().GetResult()就足够了; 不需要Task.Run

最新更新