C# 将在UI线程上运行的代码移动并访问线程的全局状态
我正试图在我的Windows Phone应用程序的UI线程中移动尽可能多的处理。当我点击一个按钮时,我有一些正在执行的代码。该代码在概念上与下面的代码类似C# 将在UI线程上运行的代码移动并访问线程的全局状态,c#,.net,multithreading,windows-phone-7,C#,.net,Multithreading,Windows Phone 7,我正试图在我的Windows Phone应用程序的UI线程中移动尽可能多的处理。当我点击一个按钮时,我有一些正在执行的代码。该代码在概念上与下面的代码类似 private int Processing(int a, int b, int c) { this.A = this.moreProcessing(a); this.B = this.moreProcessing(b); this.C = this.moreProcessing(c); int newInt =
private int Processing(int a, int b, int c) {
this.A = this.moreProcessing(a);
this.B = this.moreProcessing(b);
this.C = this.moreProcessing(c);
int newInt = /* ... */
return newInt;
}
public void Button_Click(object sender, EventArgs args) {
var result = Processing(1, 2, 3);
this.MyTextBox.Content = result;
}
如果处理方法没有设置/获取全局状态变量,那么在线程上移动代码的执行将非常容易
如何确保一次只有一个线程以正确的顺序运行?现在很容易,因为处理代码在UI线程上运行。UI线程的好处在于它可以保证一切都按正确的顺序运行,一次一个。我如何用线程复制它
我可以重构整个代码,使其几乎没有全局状态,但现在不一定这样做。我也可以用锁,但我只是想知道是否有更好的方法。我正在做的处理不是超重的。然而,我有时会在UI中看到一些延迟,我希望尽可能保持UI线程的自由
谢谢 有几种方法 如果您打算为每个按钮单击事件启动一个新线程,那么实际上您可能有多个线程希望写入相同的变量。您可以通过在
lock
语句中包装对这些变量的访问来解决这个问题
或者,您可以让一个线程始终运行,专用于处理
线程。使用a在UI线程和处理
线程之间进行通信。每当单击按钮时,将相关信息放在BlockingCollection上,并让处理线程从该BlockingCollection中拉出工作项
应接近正常的未测试代码:
class ProcessingParams // Or use a Tuple<int, int, int>
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}
BlockingCollection<int> bc = new BlockingCollection<int>();
private int Processing() {
try
{
while (true)
{
ProcesingParams params = bc.Take();
this.A = this.moreProcessing(params.A);
this.B = this.moreProcessing(params.B);
this.C = this.moreProcessing(params.C);
int newInt = /* ... */
return newInt; // Rather than 'return' the int, place it in this.MyTextBox.Content using thread marshalling
}
}
catch (InvalidOperationException)
{
// IOE means that Take() was called on a completed collection
}
}
public void Button_Click(object sender, EventArgs args) {
//var result = Processing(1, 2, 3);
bc.Add (new ProcessingParams() { A = 1, B = 2, C = 3 };
//this.MyTextBox.Content = result;
}
试试这个:
public void Button_Click(object sender, EventArgs args)
{
Button.Enabled = false;
ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundProcessing));
}
private void BackgroundProcessing(object state)
{
var result = Processing(1, 2, 3);
// Call back to UI thread with results
Invoke(new Action(() => {
this.MyTextBox.Content = result;
Button.Enabled = true;
}));
}
private int Processing(int a, int b, int c)
{
this.A = this.moreProcessing(a);
this.B = this.moreProcessing(b);
this.C = this.moreProcessing(c);
int newInt = /* ... */
return newInt;
}
一个非常简单的解决方案是使用。它允许您将工作卸载到后台线程,并在完成时通知您。(另一个选项见下文)
另一个选择是为下一版本的Windows Phone准备好代码,并开始使用任务并行库。TPL可用于.Net4,但不可用于Windows Phone。有一些确实支持Silverlight和Windows Phone。将其中一个包添加到项目中,您可以将代码更改为(语法可能不是100%正确):
private Task ProcessAsync(inta、intb、intc)
{
var taskCompletionSource=新的taskCompletionSource();
var task=task.Factory.StartNew(()=>
{
//做你的工作
返回newInt;
}
task.ContinueWith(t=>taskCompletionSource.SetResult(t.Result));
返回taskCompletionSource.Task;
}
无效按钮\单击(对象发送者、事件参数)
{
//禁用该按钮以防止再次单击
MyButton.IsEnabled=false;
var task=ProcessAsync(1,2,3);
task.ContinueWith(t=>
{
MyTextBox.Content=t.Result;
MyButton.IsEnabled=true;
});
}
如果用户“单击”的速度快于处理完成工作的速度,该怎么办?您需要在启动后台线程之前禁用您的按钮。button.Enabled=false。在设置MyTextBox.Content的地方,您可以重新启用它。我更新了代码。虽然这提高了用户体验(这是一件好事),我不会依赖UI状态来保证后台工作人员的线程安全。有一天,有人会添加一个菜单来完成与按钮今天所做的相同的事情。单独关注。让UI代码负责管理向用户的演示,处理代码负责以线程安全的方式完成其工作。然后使用控制器,MVC或MVP超出了这个问题的范围。我不是说MVC。我是说对线程代码进行编程,使其无论UI代码做什么都是线程安全的。很好的解决方案!但Windows Phone没有BlockingCollection.:(这太糟糕了!但是,在.NET中使用较旧的线程结构实现生产者/消费者队列非常简单。这里有详细的演练,或者这里有一个更灵活的版本,我推荐整个关于线程的电子书(我的参考资料)很好的示例。因此,我可以使用ContinueWith从非等待函数(如事件)调用异步?您可以使用任何一种方法。事件可以是异步方法,也可以等待调用。您只需要小心b/c现在您有一个异步void方法,这很危险。请确保始终将等待调用包装在try/catch中。请记住,这个答案已经有近1年半的历史了,所以,是否有一些改进。我知道,我不能期望事件等待async(例如,如果我在ApplicationLaunching()中有一些async init,phone将不会等待)。但是如果我有ProcessAsync和ContinueWith,并且我在其中调用rising new event,或者隐藏splashscreen,那么应该可以吗?
public void Button_Click(object sender, EventArgs args)
{
Button.Enabled = false;
ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundProcessing));
}
private void BackgroundProcessing(object state)
{
var result = Processing(1, 2, 3);
// Call back to UI thread with results
Invoke(new Action(() => {
this.MyTextBox.Content = result;
Button.Enabled = true;
}));
}
private int Processing(int a, int b, int c)
{
this.A = this.moreProcessing(a);
this.B = this.moreProcessing(b);
this.C = this.moreProcessing(c);
int newInt = /* ... */
return newInt;
}
void Button_Click(object sender, EventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
e.Result = Processing(1, 2, 3);
};
worker.RunWorkerCompleted += (s1, e1) =>
{
MyTextBox.Content = e1.Result;
MyButton.IsEnabled = true;
};
// Disable the button to stop multiple clicks
MyButton.IsEnabled = false;
worker.RunWorkerAsync();
}
private Task<int> ProcessAsync(int a, int b, int c)
{
var taskCompletionSource = new TaskCompletionSource<int>();
var task = Task.Factory.StartNew<int>(() =>
{
// Do your work
return newInt;
}
task.ContinueWith(t => taskCompletionSource.SetResult(t.Result));
return taskCompletionSource.Task;
}
void Button_Click(object sender, EventArgs args)
{
// Disable the button to prevent more clicks
MyButton.IsEnabled = false;
var task = ProcessAsync(1,2,3);
task.ContinueWith(t =>
{
MyTextBox.Content = t.Result;
MyButton.IsEnabled = true;
});
}