C# 试图理解任务。运行+;异步+;等待()/结果

C# 试图理解任务。运行+;异步+;等待()/结果,c#,asynchronous,task,C#,Asynchronous,Task,我试图理解Task.Run+Wait()+async+Wait是如何工作的。 我读过这一页,但不太明白 在我的代码中,我从Microsoft EventHub接收事件,并使用实现IEventProcessor的类处理它们。 我在ConvertToEntity()中调用DoAnotherWork()方法,这是一种async方法,它是一种同步方法。 由于方法是async,因此我使用Task.Run()和async来委托。(即Task.Run(异步()=>entities=Wait-DoAnother

我试图理解Task.Run+Wait()+async+Wait是如何工作的。
我读过这一页,但不太明白

在我的代码中,我从Microsoft EventHub接收事件,并使用实现
IEventProcessor
的类处理它们。 我在
ConvertToEntity()
中调用
DoAnotherWork()
方法,这是一种
async
方法,它是一种同步方法。 由于方法是
async
,因此我使用
Task.Run()
async
来委托。(即
Task.Run(异步()=>entities=Wait-DoAnotherWork(实体)).Wait()

代码已经运行了一段时间,但现在我的团队成员删除了
Task.Run()
,并将其更改为
DoAnotherWork(entities).Result。我不确定这是否会导致死锁。
我没有问他为什么要修改它,但这一修改让我思考“我的代码好吗?为什么?”

我的问题是:
*两者之间有什么区别?
*哪种代码合适和/或安全(=不会导致死锁)?
*如果是,在什么情况下会导致死锁?
*为什么
Task.Run()
解决我遇到的死锁?(详见下文)

注意:我使用的是.NETCore3.1

为什么使用Task.Run()
当我们使用
AbcAsync().Result
Wait()
时,我的团队有几次出现死锁问题(该方法是在NET Core Web API方法中调用的,死锁主要发生在我们运行执行该方法的单元测试时),因此我们使用了
Task.Run(异步()=>Wait AbcAsync()).Wait()/结果
之后,我们再也没有看到任何死锁问题。
但是,本页:表示在某些情况下,delagation将导致死机

public class EventProcessor : IEventProcessor
{
    public async Task ProcessEventsAsync(PartitionContext context, IEnumerable<EventData> messages) 
    {
        ...
        var result = await eventHandler.ProcessAsync(messages);
        ...
    }
}

public Task async ProcessAsync(IEnumerable<EventData> messages)
{
    ...
    var entities = ConvertToEntity(messages);
    ...
}

public List<Entity> ConvertToEntity(IEnumerable<EventData> messages)
{
    var serializedMessages = Serialize(messages);
    var entities = autoMapper.Map<Entity[]>(serializedMessages);

    // Task.Run(async () => entities = await DoAnotherWork(entities)).Wait(); // before change
    entities = DoAnotherWork(entities).Result; // after change

    return entities;
}    

public Task async Entity[] DoAnotherWork(Entity[] entities)
{
    // Do stuff async
    await DoMoreStuff(entities)...
}
公共类事件处理器:IEventProcessor
{
公共异步任务进程EventsAsync(PartitionContext上下文,IEnumerable消息)
{
...
var result=await eventHandler.ProcessAsync(消息);
...
}
}
公共任务异步进程异步(IEnumerable消息)
{
...
var实体=转换实体(消息);
...
}
公共列表转换实体(IEnumerable消息)
{
var serializedMessages=序列化(消息);
var entities=autoMapper.Map(serializedMessages);
//Task.Run(async()=>entities=Wait DoAnotherWork(entities)).Wait();//更改前
entities=DoAnotherWork(entities).Result;//更改后
返回实体;
}    
公共任务异步实体[]DoNotherWork(实体[]实体)
{
//异步做事
等待DomorSteff(实体)。。。
}
这两者的区别是什么

Task.Run
开始在线程池线程上运行委托;直接调用该方法

在学习
async
时,将所有内容分开是很有帮助的,这样您就可以准确地看到发生了什么:

entities = DoAnotherWork(entities).Result;
相当于:

var entitiesTask = DoAnotherWork(entities);
entities = entitiesTask.Result;
async Task LambdaAsMethod()
{
  entities = await DoAnotherWork(entities);
}
var runTask = Task.Run(LambdaAsMethod);
runTask.Wait();
该代码:

Task.Run(async () => entities = await DoAnotherWork(entities)).Wait();
相当于:

var entitiesTask = DoAnotherWork(entities);
entities = entitiesTask.Result;
async Task LambdaAsMethod()
{
  entities = await DoAnotherWork(entities);
}
var runTask = Task.Run(LambdaAsMethod);
runTask.Wait();
哪种代码合适和/或安全(=不会导致死锁)

在ASP.NET环境中,因为它将干扰ASP.NET对线程池的处理,并在不需要线程切换时强制执行线程切换

如果是,在什么情况下会导致僵局

这需要两件事:

  • 阻止异步代码而不是正确使用wait的代码
  • 强制同步的上下文(即,一次只允许上下文中的一个代码块)
  • 最好的解决办法是消除第一个条件;换句话说,使用。要在此处应用,最好的解决方案是完全消除阻塞:

    public Task async ProcessAsync(IEnumerable<EventData> messages)
    {
        ...
        var entities = await ConvertToEntityAsync(messages);
        ...
    }
    
    public async Task<List<Entity>> ConvertToEntityAsync(IEnumerable<EventData> messages)
    {
        var serializedMessages = Serialize(messages);
        var entities = autoMapper.Map<Entity[]>(serializedMessages);
    
        entities = await DoAnotherWork(entities);
    
        return entities;
    }
    
    public Task async ProcessAsync(IEnumerable消息)
    {
    ...
    var实体=等待ConvertToEntityAsync(消息);
    ...
    }
    公共异步任务ConvertToEntityAsync(IEnumerable消息)
    {
    var serializedMessages=序列化(消息);
    var entities=autoMapper.Map(serializedMessages);
    实体=等待完成其他工作(实体);
    返回实体;
    }
    
    为什么Task.Run()会解决我遇到的死锁?(详见下文)

    ,因此它使用线程池上下文。由于.NETCore没有上下文,它删除了死锁的第二个条件,死锁将不会发生如果您正在ASP.NET核心项目中运行此功能

    当我们使用AbcAsync().Result或.Wait()时,我的团队有几次出现死锁问题(该方法是在NET Core Web API方法中调用的,死锁主要发生在我们运行执行该方法的单元测试时)

    一些单元测试框架确实提供了一个上下文——最著名的是xUnit。xUnit提供的上下文是一个同步上下文,因此它更像是UI上下文或ASP.NET预核心上下文。因此,当您的代码在单元测试中运行时,它确实存在死锁的第二个条件,并且死锁可能会发生


    如上所述,最好的解决方案是完全消除堵塞;这将有一个很好的副作用,使您的服务器更高效。但是如果必须执行阻塞,那么您应该将单元测试代码包装在一个
    任务中。运行
    ,而不是ASP.NET核心代码。

    另外,当两个线程最终等待相同的资源或彼此时,通常会发生死锁,两条线中的一条永远不会结束或向前移动。乔·阿尔巴哈里出色而自由的作品非常详细地解释了这一切。