异步/等待 VS 任务运行:何时使用?如何使用?

10

好的,我希望我已经掌握了async/await的基础知识,但是我脑海中仍有一些问题。

现在问题来了,假设在这个简单的例子中:

static void Main(string[] args)
{

    Method();

    Console.WriteLine("Main Thread");

    Console.ReadLine();

}

public async static void Method()

{

    await Task.Run(new Action(LongTask));

    Console.WriteLine("New Thread");

}

public static void LongTask()

{

    Thread.Sleep(8000);

    Console.WriteLine("Long Task");

}

调用Method()函数后,主线程仍然继续执行,并在遇到8秒的await后打印Main Thread

因此,一旦Method()函数遇到await并保存同步上下文后,它会返回给调用者,即主函数,在那里继续执行。

它先打印Main Thread

然后在8秒完成后,依次打印Long TaskNew Thread

我已经理解了这部分内容。我的问题是在我的应用程序中:

public IList<createcaseoutput> createCase(CreateCaseInput CreateCaseInput,SaveCaseSearchInput SaveCaseSearchInput)    
{
    .............
    SQL.CaseSQL.getCreateCaseParameters(CreateCaseInput, out strSPQuery, out listParam);    
    var AcctLst = rep.ExecuteStoredProcedure<createcaseoutput>(strSPQuery, listParam).ToList();

    if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString()))

    {
        await saveCaseSearch(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
    }

    console.writeline("Async called");
    return AcctLst;    
}

public async Task<ilist<savecasesearchoutput>> saveCaseSearch(SaveCaseSearchInput SaveCaseSearchInput,Int64? case_key)

{
    ..........................
    SQL.CaseSQL.getSaveCaseSearchParameters(SaveCaseSearchInput, case_key, out strSPQuery, out listParam);

    var AcctLst = await rep.ExecuteStoredProcedureAsync<entities.case.savecasesearchoutput>(strSPQuery, listParam);

    return AcctLst;
}

这里的createCase也遇到了await,应该立即返回并执行下一行代码,在SaveCaseSearch完成之前打印Async called是吗?

好的,如果我想的没错,可能会控制返回给调用者

所以,如果我将我的调用SaveCaseSearch包装在另一个名为 SavCaseSearch 的async/await方法中,会怎样呢?

async DoWork() {....
}

直接从CreateCase()调用DoWork(),那么它会在遇到await并且完成之前,持续打印“Async called”吗?

我的想法正确吗?

另外有时我看到这些术语会感到困惑

await someAsync() 

它们之间有什么区别?应该遵循哪一个?

await Task.Run(() => someAsync())...


1
如果您正在尝试在控制台应用程序中使用async,除非正确设置,否则可能会遇到问题,否则它可能不会按预期工作。请参见http://anthonysteele.co.uk/async-and-await-with-console-apps和http://blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx。 - Bradley Uffner
"async" 并不是关于线程的问题。 - GSerg
看看 Stephen Cleary 的帖子。非常有用。 - Ian
4个回答

4
我的问题是在我的应用程序中:
你的代码无法编译,因为你在没有使用async的情况下使用了await。修正后的代码如下:
public async Task<IList<createcaseoutput>> createCaseAsync(CreateCaseInput CreateCaseInput,SaveCaseSearchInput SaveCaseSearchInput)    
{
  ...
  await saveCaseSearch(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
  console.writeline("Async called");
  return AcctLst;    
}

这里的createCase也遇到了await,它应该立即返回并执行下一行代码,甚至在SaveCaseSearch完成之前就打印出“异步调用”对吗?

不对。以下是代码:

  await saveCaseSearch(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);

这段代码与以下代码相同:

  var saveTask = saveCaseSearchAsync(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
  await saveTask;

首先,createCaseAsync会调用saveCaseSearchAsync。假设saveCaseSearchAsync正在执行一些异步操作,所以它会向createCaseAsync返回一个不完整的任务。然后createCaseAsync等待该任务,这将导致它向调用者返回一个不完整的任务。
最终,saveCaseSearchAsync会完成,这将完成它返回的任务(在上面的代码中称为saveTask)。这反过来将继续执行createCaseAsync,并且它将继续到下一行并在控制台上打印“Async called”。

那么,如果我在另一个async/await方法中包装SavCaseSearch调用呢?

您不需要包装器,因为createCaseAsync已经返回了一个Task

它们之间有什么区别?应该遵循哪一个?

Task.Run主要用于将阻塞工作从UI线程移至线程池。由于您正在使用ASP.NET,因此不要使用Task.Run

3
异步编程的第一条规则是始终使用异步或从不使用异步。
如果底层 API 无法处理异步操作,那么在上层(如 ASP.NET MVC)使用异步就没有意义,因为在等待 IO 操作(如数据库调用)时,所有线程都被占用,会导致线程饥饿问题。
您的示例是混合使用同步和异步的典型案例。Sleep 调用将锁定线程直到完成。相反,您应该使用 Task.Delay,因为它会释放线程直到延迟完成。
我给您的建议是,首先遵循我提到的第一条规则,只有在涉及 IO 绑定操作(如数据库或文件调用)时才使用异步。然后,当您更好地理解异步时,可以开始打破它,因为那时您会更好地了解它可能带来的影响。
抱歉没有直接回答您的问题,但是线程是一个复杂的主题,如果您试图直接掌握所有内容,可能会让您的大脑崩溃。所以要从小做起。

1
关于异步/等待和任务之间的区别... 异步/等待是为了简化代码而出现的语法关键字,await 关键字之前的所有操作都在调用线程中执行,await 关键字之后的所有操作都在任务的继续中执行。
使用 TPL 来设置任务将需要大量的代码,可读性会受到影响。但请注意,在底层仍然使用任务和继续。
此外,它们并不能总是代替任务,例如当任务完成是不确定的,或者您有多个任务级别以及使用了 TaskCompletionSource 时。
更多信息,请阅读本书《编写高性能 .NET 代码》第四章“异步编程”一节,作者是 Ben Watson。
还请注意,TPL 内部使用的是 .NET 线程池,但它会更加智能地执行多个任务,通过智能使用委托对象,在返回线程之前顺序执行同一线程上的多个任务。

1

这里的createCase也遇到了await,它应该立即返回并执行下一行代码,并在SaveCaseSearch完成之前打印Async called,是吗?

这甚至不会编译。'await'运算符只能在'async'方法内使用。话虽如此,如果您删除'await'运算符,则下一行将在saveCaseSearch完成之前打印“Async called”。

我想得对吗?

saveCaseSearch已经是一个'async'方法,所以您不需要包装它来实现所需的结果。话虽如此,如果您真的想要,可以将其包装在另一个方法中。

它们之间的区别是什么?应该遵循哪个?

'await'运算符等待一个Task对象,因此两者都可以。我会选择await someAsync(),因为写的代码更少。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接