Wil*_*ill 6 c# wpf asynchronous async-await
我在异步等待方面相当落后,所以这可能是一个“duh”问题。
我正在开发一个非常小的 UI 应用程序,该应用程序使用WPF NotifyIcon库从系统托盘运行。
应用程序应该以以下方式非常简单地运行(对于用户而言):
我遇到的问题是“异步执行开始”部分。在此之前发生的所有事情都运行良好,但是当程序开始尝试“运行”时,UI 会锁定(我的意思是,用户可以像疯子一样单击托盘图标,并且上下文菜单拒绝出现)。
这种锁定发生的时间长得令人无法接受。
这是启动代码:
private async void AppStartup( object sender, StartupEventArgs e ) {
this.TRSIcon = this.FindResource( "TRSIcon" ) as TaskbarIcon;
if ( Settings.Default.DoUpgrade ) { //Upgrade if necessary.
Settings.Default.Upgrade( );
Settings.Default.DoUpgrade = false;
Settings.Default.Save( );
}
if ( string.IsNullOrEmpty( Settings.Default.Username ) || string.IsNullOrEmpty( Settings.Default.Password ) ) {
new Help( ).ShowDialog( );
Tuple<string, string> UP;
if ( ( UP = Login.Instance.GetUserPassword( ) ) != null ) {
Settings.Default.Username = UP.Item1;
Settings.Default.Password = UP.Item2;
Settings.Default.Save( );
} else
return;
}
await this.Start( ); //<-----This is where the meat of the program runs and it hangs the UI until it finishes.
return; //<-----This is just so that I have a break point to see that await this.Start is blocking (I have to do it like that right? or do I?)
}
Run Code Online (Sandbox Code Playgroud)
这是Resources.xaml
:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Tools="clr-namespace:WPFTools.TaskbarNotification;assembly=WPFTools"
xmlns:TR="clr-namespace:TriviaRetriever">
<ContextMenu x:Key="TSRInterfaceMenu" x:Shared="false">
<MenuItem Header="Login" Command="{Binding cmdLogin}"/>
<MenuItem Header="Get My Trivia" Command="{Binding cmdDownload}"/>
<MenuItem Header="Register" Command="{Binding cmdRegister}"/>
<MenuItem Header="Lost Password" Command="{Binding cmdLostPassword}"/>
<MenuItem Header="About" Command="{Binding cmdAbout}"/>
<MenuItem Header="Log Out" Command="{Binding cmdLogout}"/>
<MenuItem Header="Exit" Command="{Binding cmdExit}"/>
</ContextMenu>
<Tools:TaskbarIcon
x:Key="TRSIcon"
MenuActivation="LeftOrDoubleClick"
IconSource="/TRIcon.ico"
DoubleClickCommand="{Binding cmdAbout}"
ContextMenu="{StaticResource TSRInterfaceMenu}">
<Tools:TaskbarIcon.DataContext>
<TR:TRSIViewModel/>
</Tools:TaskbarIcon.DataContext>
</Tools:TaskbarIcon>
</ResourceDictionary>
Run Code Online (Sandbox Code Playgroud)
这是上下文菜单命令的 MVVM:
public class TRSIViewModel {
public ICommand cmdLogin {
get {
return new DelegateCommand {
fncCanExecute = ( ) => ( Application.Current as App ).Core == null,
actCommand = async ( ) => {
Tuple<string, string> LoginPassword = Login.Instance.GetUserPassword( );
if ( LoginPassword != null ) {
Settings.Default.Username = LoginPassword.Item1;
Settings.Default.Password = LoginPassword.Item2;
Settings.Default.Save( );
await ( Application.Current as App ).Start( );
}
}
};
}
}
public ICommand cmdLogout {
get {
return new DelegateCommand {
fncCanExecute = ( ) => ( Application.Current as App ).Core != null,
actCommand = ( ) => {
( Application.Current as App ).Core.Terminate( );
( Application.Current as App ).Core = null;
}
};
}
}
public ICommand cmdRegister {
get {
return new DelegateCommand {
fncCanExecute = ( ) => true,
actCommand = ( ) => Process.Start( @"https://www.digigames.com/weekly_subscriptions/index.php" )
};
}
}
public ICommand cmdLostPassword {
get {
return new DelegateCommand {
fncCanExecute = ( ) => true,
actCommand = ( ) => Process.Start( @"https://www.digigames.com/weekly_subscriptions/lost_password.php" )
};
}
}
public ICommand cmdAbout {
get {
return new DelegateCommand {
fncCanExecute = ( ) => true,
actCommand = ( ) => ( Application.Current as App ).TRSIcon.ShowCustomBalloon( new About( ), PopupAnimation.Slide, 5000 )
};
}
}
public ICommand cmdExit {
get {
return new DelegateCommand {
fncCanExecute = ( ) => true,
actCommand = ( ) => {
if ( ( Application.Current as App ).Core != null )
( Application.Current as App ).Core.Terminate( );
Application.Current.Shutdown( 0 );
}
};
}
}
public ICommand cmdDownload {
get {
return new DelegateCommand {
fncCanExecute = ( ) => ( Application.Current as App ).Core != null,
actCommand = async ( ) => await ( Application.Current as App ).Core.DownloadTrivia( true )
};
}
}
public class DelegateCommand : ICommand {
public Action actCommand { get; set; }
public Func<bool> fncCanExecute { get; set; }
public bool CanExecute( object parameter ) {
return this.fncCanExecute != null && this.fncCanExecute( );
}
public event EventHandler CanExecuteChanged {
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute( object parameter ) { this.actCommand( ); }
}
}
Run Code Online (Sandbox Code Playgroud)
我在这里做错了什么?
我认为你的问题完全是关于你的Start
方法。
但首先要说的是。您的断点没有执行您期望的操作。一旦该Start
方法实际完成并执行剩余的函数,它就会中断,而不是在 UI 线程再次释放时中断。您必须了解,一旦执行离开 UI 同步,UI 线程就可以再次自由运行。Start
。
要了解该方法实际释放执行需要多长时间,一个好方法是等待它返回Task
。
var pendingTask = this.Start();\nDebugger.Break();\nawait pendingTask;\n
Run Code Online (Sandbox Code Playgroud)\n\nTask
一旦该Start
方法命中内部异步执行的函数,该对象就会返回。await
一旦返回pendingTask
。
就你而言,我认为时间会相似,因为Start
方法没有向后台发送足够的工作。
有几种方法可以解决这个问题。如果你的Start
方法不与 UI 交互,那就没问题。您只需将整个方法发送到后台即可完成。这工作起来很简单:
await Task.Run(() => this.Start());\n
Run Code Online (Sandbox Code Playgroud)\n\n这会将任务发送到 ThreadPool 的线程中,并立即再次释放 UI。该Task.Run
方法有一个重载,可以自动解开Task
该Start
方法返回的内部内容。
如果您的方法与 UI 交互,则必须在内部更改该方法。查找方法内需要很长时间且不与 UI 交互的部分,并将它们包装到方法的调用中,Task.Run
如上所示。
每一个await
都会再次建立SynchronizationContext
之前存在的。因此,线程中每个await
能够更改 UI 的线程都将确保延续也在同一个线程中执行。
所以像这样的事情工作没有问题:
\n\nsomeLabel.Label = "Working\xe2\x80\xa6";\nawait Task.Run(() => DoManyThings());\nsomeLabel.Label = "Done! :D"\n
Run Code Online (Sandbox Code Playgroud)\n\n我希望这有帮助。如果不知道你的方法的作用,我无法给你更多提示Start
。但我希望这个答案能让你走上正轨。
我通常的免责声明:我通常使用 VB.net,因此我的 C# 代码可能在语法方面存在缺陷。如果您发现任何错误,请随时编辑它或告诉我出了什么问题。
\n