我正试图了解C#中新的异步功能,到目前为止,我注意到的最奇怪的事情是异步功能的每个示例都有一个等待另一个异步的功能在框架中定义的函数,但它们都没有自定义代码。
例如,我想要的是从文本文件中的每一行创建一个对象,但是异步,以便UI线程不会冻结:
async Task Read()
{
string[] subjectStrings = File.ReadAllLines(filePath);
for (int i = 0; i < subjectStrings.Length; i++)
{
Task<Subject> function = new Task<Subject>(code => new Subject((string)code), subjectStrings[i]);
try
{
Subject subject = await function;
subjects.Add(subject);
}
catch (Exception ex)
{
debugWriter.Write("Error in subject " + subjectStrings[i]);
continue;
}
}
}
如您所见,我定义了一个基于文本文件中的行创建新Subject
对象的任务,然后等待此任务。如果我这样做,调试器会到达await
行,然后停止。据我所知,不再运行代码了。
如果我使用的是旧的异步功能,我只需使用Task.ContinueWith()并添加一个回调lambda,将主题添加到列表中,然后继续使用。
所以我的问题是:
await
,除非您处于异步函数中,并且您不应该在没有等待的情况下调用异步方法,那么如何从同步方法中调用该方法呢?答案 0 :(得分:5)
你不是开始任务 - 所以它永远不会完成。
使用Task.Run
代替new Task
,它会为您创建并启动任务。
请注意,您仍在同步阅读该文件,这不太理想......如果您的Subject
构造函数真的需要很长时间才能完成,我会质疑是否它应该是一个构造函数。
答案 1 :(得分:1)
为什么这段代码不起作用?您如何制作不使用任何异步方法的自定义异步方法?
使用await Task.Run
或await Task.Factory.StartNew
创建并运行任务。调用new Task
会创建一个尚未启动的任务。在大多数情况下,这是不必要的,但您可以对以这种方式创建的任务调用Start
。
你应该如何使用异步方法?除非你处于异步函数中,否则不能使用await,并且你不应该在没有等待的情况下调用异步方法,那么如何从同步方法中调用该方法呢?
适当的“root”异步调用取决于应用程序的类型:
在控制台应用程序中:Wait
返回Task
。
在GUI应用程序中:使用async void
事件处理程序。
在ASP.NET MVC中:控制器可以返回Task
。
答案 2 :(得分:1)
你应该如何制作一个不使用任何异步方法的自定义异步方法?
你没有。如果该方法没有异步工作要做,它应该是同步的;它不应该是async
。
核心是,所有async
方法都归结为两种方法之一。他们要么通过类似Task.Run
(不推荐用于库代码)的方式将工作排队到线程池,要么通过TaskCompletionSource<T>
或Task.Factory.FromAsync
等快捷方式执行真正的异步工作。
你应该如何使用异步方法?除非你处于异步函数中,否则不能使用await,并且你不应该在没有等待的情况下调用异步方法,那么如何从同步方法中调用该方法呢?
你没有。理想情况下,您应始终async
。控制台应用程序是此规则的一个例外;他们 拥有同步Main
。但是你应该在WinForms,WPF,Silverlight,Windows Store,ASP.NET MVC,WebAPI,SignalR,iOS,Android和Windows Phone应用程序以及单元测试中一直使用async。
您可以通过async
和await
和Task.WhenAll
等组合器使用Task.WhenAny
方法。这是使用async
方法的最常用方法,但不是唯一方法;例如,您可以调用async
方法并将其作为IObservable<T>
使用。
答案 3 :(得分:0)
您感到困惑等待和正常工作。等待使用async
/ await
,工作没有。这仍然意味着你可以await
一个CPU绑定的任务,但你必须手动运行它,例如:
var result = await Task.Run(YourLongOperation);
有助于我在直觉上理解这一点的区别是,等待是合作的 - 我自愿放弃了我的CPU时间份额,因为我实际上并不需要它。另一方面,工作必须并行运行。
在正常情况下,仅使用固有的异步async
/ await
,不一定要超过您开始使用的单个线程。将CPU绑定操作与受I / O限制的操作相结合通常是一个坏主意(除非您明确地并行运行任务,否则CPU绑定将阻塞)。
答案 4 :(得分:0)
我不会厌倦了另一种技术解释™,而是让我根据你的代码示例向你展示一个实际的例子。
从在调用线程上执行所有工作的同步版本开始。
class SubjectFactory
{
public IEnumerable<Subject> Read(string filePath)
{
string[] subjectStrings = File.ReadAllLines(filePath);
return Parse(subjectStrings);
}
private IEnumerable<Subject> Parse(IEnumerable<string> subjects)
{
string code = "XYZ";
foreach ( var subject in subjects )
{
yield return new Subject(code, subject);
}
}
}
假设Subject
中的构造函数是轻量级的,最大的瓶颈是File.ReadAllLines
。为什么?因为磁盘I / O本质上很慢。
那么你如何在任务中包装它呢?
如果框架有File.ReadAllLinesAsync()
方法,您可以等待并完成它。
public async Task<IEnumerable<Subject>> ReadAsync(string filePath)
{ // Doesn't exist!
string[] subjectStrings = await File.ReadAllLinesAsync(filePath);
return this.Parse(subjectStrings);
}
不幸的是,生活很艰难,并行编程也是如此。看起来你必须重新发明轮子。
private async Task<string[]> ReadAllLinesAsync(string filePath)
{
ArrayList allLines = new ArrayList();
using ( var streamReader = new StreamReader(File.OpenRead(filePath)) )
{
string line = await streamReader.ReadLineAsync();
allLines.Add(line);
}
return (string[]) allLines.ToArray(typeof(string));
}
现在,您可以执行与以前相同的操作,但请使用自定义方法ReadAllLinesAsync()
。
public async Task<IEnumerable<Subject>> ReadAsync(string filePath)
{
// call with 'this' instead of 'File'
string[] subjectStrings = await this.ReadAllLinesAsync(filePath);
return Parse(subjectStrings);
}
有了这些,在您的WPF应用程序中,您所要做的就是:
var filePath = @"X:\subjects\";
var subjectFactory = new SubjectFactory();
var subjectsCollection = await subjectFactory.ReadAsync(filePath);
var observableCollection = new ObservableCollection<Subject>(subjectsCollection);