我正在尝试从普通方法运行“异步”方法:
public string Prop
{
get { return _prop; }
set
{
_prop = value;
RaisePropertyChanged();
}
}
private async Task<string> GetSomething()
{
return await new Task<string>( () => {
Thread.Sleep(2000);
return "hello world";
});
}
public void Activate()
{
GetSomething.ContinueWith(task => Prop = task.Result).Start();
// ^ exception here
}
抛出的异常是:
启动可能不会在继续任务上调用。
这意味着什么呢?如何在后台线程上运行异步方法,将结果发送回UI线程?
修改
还尝试Task.Wait
,但等待永远不会结束:
public void Activate()
{
Task.Factory.StartNew<string>( () => {
var task = GetSomething();
task.Wait();
// ^ stuck here
return task.Result;
}).ContinueWith(task => {
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
GetSomething.ContinueWith(task => Prop = task.Result).Start();
}
答案 0 :(得分:35)
专门修复您的示例:
public void Activate()
{
Task.Factory.StartNew(() =>
{
//executes in thread pool.
return GetSomething(); // returns a Task.
}) // returns a Task<Task>.
.Unwrap() // "unwraps" the outer task, returning a proxy
// for the inner one returned by GetSomething().
.ContinueWith(task =>
{
// executes in UI thread.
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
这样可行,但这是老派。
在后台线程上运行某些东西并发送回UI线程的现代方法是使用Task.Run()
,async
和await
:
async void Activate()
{
Prop = await Task.Run(() => GetSomething());
}
Task.Run
将在线程池线程中启动。当你await
时,它会自动返回启动它的执行上下文。在这种情况下,您的UI线程。
您通常不需要致电Start()
。首选async
种方法,Task.Run
和Task.Factory.StartNew
- 所有方法都会自动启动任务。使用await
或ContinueWith
创建的续订也会在其父项完成时自动启动。
答案 1 :(得分:1)
好的,Cory知道如何让我重写答案:)。
所以主要的罪魁祸首实际上是FromCurrentSynchronizationContext! 只要StartNew或ContinueWith在这种调度程序上运行,它就会在UI线程上运行。有人可能会想:
好的,让我们在UI上开始后续操作,更改一些控件,生成一些操作。但是从现在开始TaskScheduler.Current不为null,如果任何控件有一些事件,那么会产生一些期望在ThreadPool上运行的StartNew,然后从那里出错。 UI aps通常很复杂,不耐烦以保持确定性,没有任何东西可以调用另一个StartNew操作,这里有一个简单的例子:
public partial class Form1 : Form
{
public static int Counter;
public static int Cnt => Interlocked.Increment(ref Counter);
private readonly TextBox _txt = new TextBox();
public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "ThreadUI!";
//this seems to be so nice :)
_txt.TextChanged += (sender, args) => { TestB(); };
WriteTrace("Form1"); TestA(); WriteTrace("Form1");
}
private void TestA()
{
WriteTrace("TestA.Begin");
Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
.ContinueWith(t =>
{
WriteTrace("TestA.ContinuWith");
_txt.Text = @"TestA has completed!";
}, TaskScheduler.FromCurrentSynchronizationContext());
WriteTrace("TestA.End");
}
private void TestB()
{
WriteTrace("TestB.Begin");
Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
WriteTrace("TestB.End");
}
}
请注意,任务返回:
无法启动!他们已经是热门的任务......