从任意(同步)方法开始 async/await

Starting async/await from any arbitrary (synchronous) method

如果我有一个常规方法调用并且我需要 'start' 使用 async/await,启动它的最佳方法是什么?没有过多的细节,我正在使用 Hangfire 来处理作业,并且由于我 认为 的一些场景不在问题的范围内,Hangfire 作业是 运行 同步,但后来我想稍后启动异步并等待,以便实际的 'job code' 可以将 async/await 用作 needed/desired。以下是启用异步和等待的最佳方式吗?

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 提出的尽可能高的阻塞建议(基本上是 first/only 跨域调用),我尝试将代码更改为以下内容:

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();

Process 函数为:

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

    await hangfireJob.Execute( inputPackage, jobContext );
}

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

Type 'System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' in Assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.

Server stack trace: at System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type) at System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context) at System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo() at 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) at System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.SmuggleIfPossible(IMessage msg) at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm) at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatchCallback(Object[] args)

Exception rethrown at [0]: at 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, IJobCancellationToken cancellationToken) in C:\BTR\Source\Evolution\BTR.Evolution.Hangfire\JobInvoker.cs:line 86

所以我改回:

public void Process( IHangfireJobContext jobContext, string inputPackageXml )

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

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

然后一切正常。将斯蒂芬的回答标记为正确。不确定为什么我不能阻止跨域的 first/only 调用,但也许这是预期的。

更新 1:AppDomain creation/reasoning

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

  1. Hangfire 开始我的工作 (JobInvoker.Invoke) 到 运行(你可以让你的工作是同步或异步工作)。所以最初,我尝试 运行 与 public async Task Invoke( XElement inputPackage, PerformContext performContext, IJobCancellationToken cancellationToken ).

  2. 一样异步
  3. JobInvoker.Invoke 通过 var appDomain = AppDomain.CreateDomain( info.ApplicationName, AppDomain.CurrentDomain.Evidence, info );

  4. 创建一个 AppDomain
  5. JobInvoker.Invoke 通过 instance = appDomain.CreateInstanceAndUnwrap( jobInvokerType.Assembly.FullName, jobInvokerType.FullName ) 创建对象。

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

  7. instance.Process 使用上面 var hangfireJob = CreateJob() 的代码通过反射创建了一个对象。此 hangfireJob 对象具有我需要在签名中具有 async 的方法。

  8. instance.Process 通过 await hangfireJob.Execute().

  9. 调用了 hangfireJob
  10. hangfireJob.Execute 签名是 public async Task Execute( XElement inputPackage, IHangfireJobContext jobContext ).

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

Type 'System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' in Assembly 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' is not marked as serializable.

所以这就是为什么我在 instance.Process 中尝试 'introducing' 异步编码(这基本上由我原来的 SynchMethod 表示),因为我的 hangfireJob.Execute 方法就是确实需要在签名上添加 async 以便我可以进行一些 await 调用。

根据这些信息,也许所有的评论都不再适用了?让我知道您是否认为他们这样做,或者它是否像 await hangfireJob.Execute( inputPackage ).GetAwaiter().GetResult() 一样简单(并摆脱 Task.Run 包装器)。

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

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

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

但是,我建议您遵循与在控制台应用程序中阻止相同的最佳做法:只在一个点阻止,尽可能在堆栈上。

就具体而言,一个GetAwaiter().GetResult()就足够了;不需要 Task.Run.