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

C# 从任意(同步)方法启动异步/等待,c#,asynchronous,async-await,hangfire,C#,Asynchronous,Async Await,Hangfire,如果我有一个常规的方法调用,并且我需要使用async/await“启动”,那么启动它的最佳方式是什么?没有太多的细节,我使用Hangfire来处理作业,由于一些我认为超出问题范围的场景,Hangfire作业是同步运行的,但是我想稍后启动async和Wait,以便实际的“作业代码”可以根据需要/期望使用async/Wait。下面是启用异步和等待的最佳方法吗 public void SynchMethod() { var inputPackage = XElement.Parse( "Nor

如果我有一个常规的方法调用,并且我需要使用async/await“启动”,那么启动它的最佳方式是什么?没有太多的细节,我使用Hangfire来处理作业,由于一些我认为超出问题范围的场景,Hangfire作业是同步运行的,但是我想稍后启动async和Wait,以便实际的“作业代码”可以根据需要/期望使用async/Wait。下面是启用异步和等待的最佳方法吗

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的“real”方法。一旦hangfireJob.Execute使用Wait,就会引发以下异常:

类型 'System.Threading.Tasks.Task'1[[System.Threading.Tasks.VoidTaskResult, mscorlib,版本=4.0.0.0,区域性=中性, PublicKeyToken=b77a5c561934e089]]“在程序集”mscorlib中, 版本=4.0.0.0,区域性=中性,PublicKeyToken=b77a5c561934e089'为 未标记为可序列化

服务器堆栈跟踪:在 System.Runtime.Serialization.FormatterServices.InternalGetSerializationMembersRuntimeType 键入 System.Runtime.Serialization.FormatterServices.GetSerializationableMemberStype 类型,StreamingContext位于 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitMemberInfo 在 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.InitSerializationObject obj,ISurrogateSelector代理选择器,StreamingContext上下文, SeroObjectInfoinit SeroObjectInfoinit,IFormatterConverter转换器, ObjectWriter ObjectWriter,SerializationBinder活页夹位于 System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo.SerializationObject obj,ISurrogateSelector代理选择器,StreamingContext上下文, SeroObjectInfoinit SeroObjectInfoinit,IFormatterConverter转换器, ObjectWriter ObjectWriter,SerializationBinder活页夹位于 System.Runtime.Serialization.Formatters.Binary.ObjectWriter.SerializationObject 图,标题[]inHeaders,\二进制编写器serWriter,布尔fCheck 在 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.SerializationStream serializationStream、对象图、头[]头、布尔fCheck 在 System.Runtime.Remoting.Channel.CrossAppDomainSerializer.SerializeMessagePartsArray列表 argstoserializeat System.Runtime.Remoting.Messaging.SnockedMethodReturnMessage..ctorIMethodReturnMessage mrm在 System.Runtime.Remoting.Messaging.SnockedMethodReturnMessage.SnockeIfPossibleMessage 味精 System.Runtime.Remoting.Channels.CrossAppDomainSink.DoDispatchByte[] reqStmBuff,走私方法调用消息走私DMCM, 走私方法返回邮件和走私信息 System.Runtime.Remoting.Channels.CrossAppDomainSink.DotTransitionDispatchCallbackObject[] args

在[0]处重试异常:在 System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessageIMessage reqMsg,IMessage retMsg at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvokeMessageData& msgData,Int32类型at BTR.Evolution.Hangfire.JobInvoker.ProcessIHangfireJobContext jobContext,字符串inputPackageXml位于 BTR.Evolution.Hangfire.JobInvoker.InvokeElement inputPackage, PerformContext PerformContext,IJobCancellationToken 中的取消令牌 C:\BTR\Source\Evolution\BTR.Evolution.Hangfire\JobInvoker.cs:第86行

所以我改回:

public void进程IHangfireJobContext,字符串inputPackageXml

并将.GetAwaiter.GetResult移动到hangfireJob的末尾。改为执行:

执行inputPackage,jobContext.GetAwaiter.GetResult

然后一切顺利。将斯蒂芬的答案标记为正确。不确定为什么我无法阻止第一个/唯一一个跨域呼叫,但这可能是意料之中的

更新1:AppDomain创建/推理

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

Hangfire启动了我的job JobInvoker。要运行Invoke,您可以将作业设置为同步或异步作业。因此,最初,我尝试像公共异步任务调用XElement inputPackage、PerformContext、PerformContext、IJobCancellationToken cancellationToken一样异步运行

JobInvoker.Invoke通过变量AppDomain=AppDomain.CreateDomain info.ApplicationName、AppDomain.CurrentDomain.Evidence、info创建AppDomain

JobInvoker.Invoke通过instance=appDomain.CreateInstanceAndUnwarp jobInvokerType.Assembly.FullName、jobInvokerType.FullName创建对象

我尝试调用实例.Process方法,该方法 签名公共异步任务进程IHangfireJobContext,字符串inputPackageXml

进程通过反射创建了一个对象,代码来自上面的var hangfireJob=CreateJob。这个hangfireJob对象具有我需要在签名中使用async的方法

进程通过wait hangfireJob.Execute调用hangfireJob

hangfireJob.Execute签名是公共异步任务Execute XElement inputPackage,IHangfireJobContext

然后代码看起来更好了,因为我在我所有的方法上都加入了异步,并且可以随意使用wait。但当hangfireJob.Execute尝试使用Wait时,我收到以下异常请记住这是在单独的AppDomain中运行的,因此出现异常:

类型 'System.Threading.Tasks.Task'1[[System.Threading.Tasks.VoidTaskResult, mscorlib,版本=4.0.0.0,区域性=中性, PublicKeyToken=b77a5c561934e089]]“在程序集”mscorlib中, 版本=4.0.0.0,区域性=中性,PublicKeyToken=b77a5c561934e089'为 未标记为可序列化

这就是为什么我尝试从instance.Process中“引入”异步编码的原因,它本质上由我最初的SynchMethod表示,因为我的hangfireJob.Execute方法真正需要在签名上使用async,以便我可以执行一些等待调用


鉴于这些信息,也许所有的评论都不再适用了?让我知道你是否认为他们会这样做,或者它是否像执行Wait hangfireJob一样简单。执行inputPackage.GetWaiter.GetResult并摆脱任务。运行包装器。

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

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

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

但是,我建议您遵循与在控制台应用程序中阻塞相同的最佳实践:只在一个点上阻塞,尽可能向上阻塞


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

这段代码没有意义,为什么不直接挂起FireJob.ExecuteinputPackage.GetWaiter.GetResult;?我怀疑你对Wait的实际功能没有一个清晰的理解。wait是return,就像迭代器块中的yield是return一样。等待只是意味着如果此任务尚未完成,请注册此方法的其余部分以便稍后运行,然后立即返回。等待不会开始任何事情;如果启动的任务尚未完成,则等待返回。我不明白为什么代码不是简单的公共异步任务MyMethod{await CreateJobParseWhather;},故事结束。@EricLippert:也许你通过更新看到了我的部分误解,但你的第一条评论很有启发性……我想我之所以没有按照你在第二条评论中的建议去做,是因为对MyMethod的调用需要等待,但方法不是异步的……所以在某个时候,我只是想“启用”使用async/Wait的能力。我认为.GetAwaiter.GetResult就是问题所在,尽管更新2中描述的行为有点奇怪,可能是因为我缺乏AppDomain知识。考虑到描述AppDomain创建的问题更新,你能告诉我你在问题更新末尾提到的答案是否仍然适用吗?@Terry:AFAIU,它应该可以正常工作,因为不会有任何任务跨越AppDomains。谢谢。考虑到我的情况,你认为你关于“遵循相同的最佳实践”的评论在这种情况下会被否决吗?或者更准确地说,这是我能达到的最高点?@Terry:听起来不错。从技术上讲,任务可以跨越内存中的应用程序域,但在我看来这是一件痛苦的事情,不值得。你可以看到我的“更新2”中出现了奇怪的情况,希望在尝试使用.GetAwaiter.GetResult时能找到解决方案。如果解决方案不正确,请告诉我。再次感谢你的建议。