多线程:使用Thread.Join()后应用程序卡死

4

我知道使用.Join()会导致线程暂停并等待其他线程完成工作,但如何避免UI界面被冻结呢?以下是我的代码:

Thread dataThread = new Thread(()=> data = getData(id));
dataThread.Start();
dataThread.Join();

Thread storingThread = new Thread(()=> storeData(data));
storingThread.Start();

我需要使用Join方法,因为第一个线程返回一个包含数据的对象,需要通过第二个线程进行存储。但这会导致UI冻结。如何在后台线程中实现这些?你们认为我应该做出什么改变?


你正在使用哪个版本的框架?>= 4.5吗? - Florian Moser
是的,伙计 @FlorianMoser - Jay
你可能想要探索async/await关键字的能力。我将在一秒钟内发布一个答案。 - Florian Moser
@FlorianMoser 抱歉,我在运行4.0版。但是,告诉我吧 :) - Jay
为什么要创建一个线程,然后立即加入它呢?如果您是新手并且正在创建一个WinForms应用程序,请考虑使用BackgroundWorker。 - user585968
5个回答

4
如果您使用的是 .Net framework >= 4.5 版本,则可以使用任务(Tasks)。
await Task.Run(() => data = getData(id));
await Task.Run(() => storeData(data));

或者使用一个命令

await Task.Run(() => storeData(getData(id)));

如果您不必等到完成,也可以执行以下操作:

Task.Run(() => storeData(getData(id)));

这会导致一个错误: 'await' 运算符只能在异步方法中使用。请考虑使用 'async' 修饰符标记此方法,并将其返回类型更改为 'Task'。 - Jay
1
@Earthling 在你的函数中添加 async 修饰符。 例如,如果你的普通函数是 private void DoStuff(),那么将其改为 private async void DoStuff() - Simon
1
@Simon 如果他正在使用 .Net 4,那么可以使用 Task.Factory.StartNew 而不需要 await。 - ΩmegaMan
没问题,我已经修好了 :) - Jay

2

看起来你不需要两个线程:

Thread dataThread = new Thread(() => storeData(getData(id)));
dataThread.Start();

请注意,TaskThread更好。此外,您可能应该使用await

1
我认为这应该是被接受的答案。特别是当它在.NET 4下编译时。此外,使用单个线程可能更加优雅。 - user585968
@MickyDuncan 这也可以,但我使用了Task而不是Threads,似乎成功解决了问题! :) - Jay
启动新线程虽然可行,但可能会带来不必要的开销。任务并不一定在新线程上启动(节省开销)。请参见https://dev59.com/jmYr5IYBdhLWcg3wvspA#13429164(进行额外解释)。 - Measurity
当然,我认为这不是一个好的解决方案。这是使他的代码工作所需的最小增量。他把事情搞得比必要的更加复杂了。 - usr
它满足了 OP 的要求,而不需要进行彻底的重写,并且支持 .NET4。 - user585968

2

将整个工作放入一个线程中,这样 UI 就不会停止:

 ThreadPool.QueueUserWorkItem( () => storeData(getData(id)));

或者针对 .Net 4

Task.Factory.StartNew(() => storeData(getData(id)));

2
在使用之前,您只需执行ThreadPool.QueueUserWorkItem(() => storeData(getData(id))); - Simon
@Simon...我同意,让我改变答案。 - ΩmegaMan
@MickyDuncan 我并不反对你所说的,我的目标只是为了让用户将操作从GUI线程中移出,以改变系统卡顿的外观。我认为他可以稍后优化这个过程。 - ΩmegaMan
1
通过使用ThreadPool.QueueUserWorkItem,您已经在“GUI线程”之外了。 - user585968
@MickyDuncan,我真正理解了你的意思...<起初当我发布答案时>,我不确定用户是否在这些线程中做了其他事情但没有显示(?), 所以我只是复制了该过程。但我感谢所有人的评论并进行了调整。 :-) - ΩmegaMan
显示剩余2条评论

2
使用async / await关键字。以下是一个简短的示例代码:
private async void Method()
{
     var result = await ExecuteAsync();
     // result == true 
}

private async Task<bool> ExecuteAsync()
{
     //run long running action
     return true;
}

在.NET 4.0中,您需要安装Microsoft.Bcl.Async才能使用此功能。
关于这个功能的很好的介绍可以在http://blog.stephencleary.com/2012/02/async-and-await.html上阅读。

通常情况下,你应该将方法标记为 async void 的唯一时机是用于事件处理程序,例如 UI。考虑改为使用 async Task。_使用 async void 方法时,没有 Task 对象,因此从 async void 方法抛出的任何异常都将直接在启动 async void 方法时处于活动状态的同步上下文上引发_。 - user585968
我知道。我相信Earthling在用户输入后调用了他上面提到的那两个操作。我的Method()将是事件处理程序。 - Florian Moser

2
答案已经给出。作为额外的,我也会给出我的答案。
您也可以像这样使用ContinueWith:
Task<string>.Factory.StartNew(() => "Hey!").ContinueWith(t => Console.WriteLine(t.Result));

这有点过头了。用户甚至还不知道TPL。 - Simon
@Simon 我只是随便提一下。这是普通的C#。是否过度设计有争议。尽管我同意它更加先进。 - Measurity
这与 OP 的问题中提供的 getData()storeData 上下文有何关联? - user585968
@MickyDuncan 他可以用 Task<TYPE_OF_DATA>.Factory.StartNew() => getData(id)).ContinueWith(t => storeData(t.Result)) 替换 Task<string>,并且适用于 .net 4。 - Measurity
真的,但考虑重写它,以便OP不需要进行任何重写。谢谢。 - user585968
@MickyDuncan Stackoverflow并不是为了提供简单的答案而存在(尽管我经常这样做)。我希望用户有时能够自己进行一些研究,这样他们才能真正学到东西。 - Measurity

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