如果我有一个常规的方法调用,并且我需要使用 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 并跨域调用。
Hangfire 启动我的作业 (
JobInvoker.Invoke
) 运行(你可以让你的作业是同步或异步作业)。 所以最初,我试图像public async Task Invoke( XElement inputPackage, PerformContext performContext, IJobCancellationToken cancellationToken )
一样异步运行。JobInvoker.Invoke
通过var appDomain = AppDomain.CreateDomain( info.ApplicationName, AppDomain.CurrentDomain.Evidence, info );
创建应用程序域JobInvoker.Invoke
通过instance = appDomain.CreateInstanceAndUnwrap( jobInvokerType.Assembly.FullName, jobInvokerType.FullName )
创建对象。我尝试调用具有签名
public async Task Process( IHangfireJobContext jobContext, string inputPackageXml )
的instance.Process
方法。instance.Process
通过反射创建了一个对象,其中包含上面的代码var hangfireJob = CreateJob()
。 这个hangfireJob
对象具有我需要在签名中async
的方法。instance.Process
通过await hangfireJob.Execute()
称为hangfireJob。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
。