在异步按钮点击时重用 UI 处理代码
Reuse UI handling code on async button clicks
情况
在 client/server 体系结构中,在长时间的 运行 操作(例如服务器端通信)期间停用 UI 控件是很常见的。 This answer shows a dedicated solution 一个非常具体的情况。
// condensed code listing from linked answer
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
DisableUI();
try { await ViewModel.CreateMessageCommand(); }
finally { EnableUI(); }
}
有没有办法generalise/abstract这种操作并重用呢?此代码在某些应用程序中往往会重复多次。然而,它遵循 Stephen Cleary 的文章指出的最佳实践,喜欢一直使用 async
并且更喜欢 async Task
而不是 async void
除了事件处理,例如此按钮单击处理程序。 Async Await Best Practices
假设有以下部分可能很长的 运行 代码:
// somewhere in an appropriate scope
async Task TheWorkWeGotToDo() { Task.Delay(2000); }
测试了一些尝试以围绕这个可能很长的 运行 操作集中 UI 启用和禁用代码的包装:
尝试 1
class OurButton extends SomeKnownButton {
Action theWork;
public async void HandleClick(object sender, EventArgs args) {
DisableUI();
try { await theWork(); }
finally { EnableUI(); }
}
此尝试不允许 await
声明的操作并抛出编译错误,因为滥用了非 async Action
实例。
尝试 2
class OurButton extends SomeKnownButton {
Task theWork;
public async void HandleClick(object sender, EventArgs args) {
DisableUI();
try { await theWork; }
finally { EnableUI(); }
}
此尝试允许 await
指定为 Task
的工作,并允许将任务的创建与其执行分开,正如 Microsoft 的 async await
初学者所指出的那样。 Async-Await Beginners Article from Microsoft
// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
这可以编译,但很难定义应该包装的 required 工作。针对这种情况的一些不令人满意的尝试是:
尝试 2a
OurButton b = /* init */;
b.theWork = TheWorkWeGotToDo; // compile error
尝试 2b
OurButton b = /* init */;
b.theWork = new Task(() => TheWorkWeGotToDo());
这可以编译,但是它没有按预期运行。
尝试 3
另一种可能性是在按钮 class 中声明一个 virtual
成员。子classes可以使用async
关键字实现这个操作。
class T {
async void Test() { await AsyncWork(); }
public virtual async Task AsyncWork() {} // warning: will run synchronously
}
class WrappedOperation : T {
public override async Task AsyncWork() { await TheWorkWeGotToDo(); }
}
从按钮 class 推导每种情况似乎很不合适。
问题
谈及一些功能和面向方面的编程背景:是否有可能以某种方式修饰函数以异步截取一部分代码?
OurButton b = /* init */;
Action someWork = /* some long running task */;
Action guardedWork = someWork.SurroundWithUiGuards(b);
b.Clicked += guardedWork;
// in some appropriate scope
static class Extension {
public static Action SurroundWithUiGuards(this Action work, SomeKnownButton b) {
return () => {
b.Disable();
try { work(); }
finally { b.Enable(); }
};
}
}
所以在这种情况下:是否有可能采用 Action
之类的 someWork
并环绕拦截代码来保护 ui 元素和 returns 一个新的'Action'实例?
备注
这是一个有点开放的问题,解决具体问题就足够了。但是,非常感谢回答抽象问题的任何建议!当前目标环境是单线程ui框架,Xamarin。
我认为主要问题是一方面似乎无法将异步操作声明为字段。另一方面,可以声明异步 lambda 表达式,但是如何等待这些表达式呢?我也希望我正在监督一些非常明显的事情。
我真的是 c# async await
编程的新手,但我已经阅读了大量 async
和 await
文章。
实际可行的解决方案是使用 Task
对象定义所有内容。 Task
s 可以等待,因此指出一个解决方案,用拦截代码包装一个长 运行 操作:
static class Ext {
public static async Task SurroundWithUIGuard(this Task task, SomeButton button) {
button.Enabled = false;
try { await task; }
finally { button.Enabled = true; }
}
}
这是将任务创建与其执行分开并通过修改 Task
实例环绕其前后代码的功能方法。
要定义一个事件处理程序,只需添加一些异步和等待表达式。
SomeButton b = /* init */;
b.Clicked += async (s,e) => await Task.Delay(5000).SurroundWithUIGuard(b);
此外,async
lambda 表达式是这一系列 async Task
操作中唯一的 async void
,因此遵循了最佳实践。由于大量使用 async await
关键字,它看起来仍然有点冗长,但允许重用这部分代码。
情况
在 client/server 体系结构中,在长时间的 运行 操作(例如服务器端通信)期间停用 UI 控件是很常见的。 This answer shows a dedicated solution 一个非常具体的情况。
// condensed code listing from linked answer
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
DisableUI();
try { await ViewModel.CreateMessageCommand(); }
finally { EnableUI(); }
}
有没有办法generalise/abstract这种操作并重用呢?此代码在某些应用程序中往往会重复多次。然而,它遵循 Stephen Cleary 的文章指出的最佳实践,喜欢一直使用 async
并且更喜欢 async Task
而不是 async void
除了事件处理,例如此按钮单击处理程序。 Async Await Best Practices
假设有以下部分可能很长的 运行 代码:
// somewhere in an appropriate scope
async Task TheWorkWeGotToDo() { Task.Delay(2000); }
测试了一些尝试以围绕这个可能很长的 运行 操作集中 UI 启用和禁用代码的包装:
尝试 1
class OurButton extends SomeKnownButton {
Action theWork;
public async void HandleClick(object sender, EventArgs args) {
DisableUI();
try { await theWork(); }
finally { EnableUI(); }
}
此尝试不允许 await
声明的操作并抛出编译错误,因为滥用了非 async Action
实例。
尝试 2
class OurButton extends SomeKnownButton {
Task theWork;
public async void HandleClick(object sender, EventArgs args) {
DisableUI();
try { await theWork; }
finally { EnableUI(); }
}
此尝试允许 await
指定为 Task
的工作,并允许将任务的创建与其执行分开,正如 Microsoft 的 async await
初学者所指出的那样。 Async-Await Beginners Article from Microsoft
// Signature specifies Task<TResult>
async Task<int> TaskOfTResult_MethodAsync()
{
int hours;
// . . .
// Return statement specifies an integer result.
return hours;
}
// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();
这可以编译,但很难定义应该包装的 required 工作。针对这种情况的一些不令人满意的尝试是:
尝试 2a
OurButton b = /* init */;
b.theWork = TheWorkWeGotToDo; // compile error
尝试 2b
OurButton b = /* init */;
b.theWork = new Task(() => TheWorkWeGotToDo());
这可以编译,但是它没有按预期运行。
尝试 3
另一种可能性是在按钮 class 中声明一个 virtual
成员。子classes可以使用async
关键字实现这个操作。
class T {
async void Test() { await AsyncWork(); }
public virtual async Task AsyncWork() {} // warning: will run synchronously
}
class WrappedOperation : T {
public override async Task AsyncWork() { await TheWorkWeGotToDo(); }
}
从按钮 class 推导每种情况似乎很不合适。
问题
谈及一些功能和面向方面的编程背景:是否有可能以某种方式修饰函数以异步截取一部分代码?
OurButton b = /* init */;
Action someWork = /* some long running task */;
Action guardedWork = someWork.SurroundWithUiGuards(b);
b.Clicked += guardedWork;
// in some appropriate scope
static class Extension {
public static Action SurroundWithUiGuards(this Action work, SomeKnownButton b) {
return () => {
b.Disable();
try { work(); }
finally { b.Enable(); }
};
}
}
所以在这种情况下:是否有可能采用 Action
之类的 someWork
并环绕拦截代码来保护 ui 元素和 returns 一个新的'Action'实例?
备注
这是一个有点开放的问题,解决具体问题就足够了。但是,非常感谢回答抽象问题的任何建议!当前目标环境是单线程ui框架,Xamarin。
我认为主要问题是一方面似乎无法将异步操作声明为字段。另一方面,可以声明异步 lambda 表达式,但是如何等待这些表达式呢?我也希望我正在监督一些非常明显的事情。
我真的是 c# async await
编程的新手,但我已经阅读了大量 async
和 await
文章。
实际可行的解决方案是使用 Task
对象定义所有内容。 Task
s 可以等待,因此指出一个解决方案,用拦截代码包装一个长 运行 操作:
static class Ext {
public static async Task SurroundWithUIGuard(this Task task, SomeButton button) {
button.Enabled = false;
try { await task; }
finally { button.Enabled = true; }
}
}
这是将任务创建与其执行分开并通过修改 Task
实例环绕其前后代码的功能方法。
要定义一个事件处理程序,只需添加一些异步和等待表达式。
SomeButton b = /* init */;
b.Clicked += async (s,e) => await Task.Delay(5000).SurroundWithUIGuard(b);
此外,async
lambda 表达式是这一系列 async Task
操作中唯一的 async void
,因此遵循了最佳实践。由于大量使用 async await
关键字,它看起来仍然有点冗长,但允许重用这部分代码。