英文:
Setting TaskCompletionSource result in RelayCommand not triggering awaited code
问题
I tracked down the problem to an unrelated issue (the view and the logic were looking at different ViewModel instances. oops.)
我追踪到问题出在一个无关的问题上(视图和逻辑正在查看不同的ViewModel实例。糟糕。)
I have the following code in a WinUI 3 application, using CommunityToolkit.MVVM, trying to use TaskCompletionSource
with RelayCommand
to control application flow around user input. It's working in one spot, and not in another.
我在一个WinUI 3应用程序中使用CommunityToolkit.MVVM,尝试在用户输入周围使用TaskCompletionSource
和RelayCommand
来控制应用程序流程。它在一个地方工作正常,而在另一个地方则不工作。
Edit: Following up on Andrew's answer, I've discovered it works fine if I call the Next method from code, rather than from the UI (using the generated NextCommand.) I still don't know why it works for the first one and not the second. I've tried using both ConfigureAwait(false)
and TaskCreationOptions.RunContinuationsAsynchronously
, to no avail.
编辑:根据Andrew的回答,我发现如果我从代码中调用Next方法,而不是从UI中调用(使用生成的NextCommand),它可以正常工作。我仍然不知道为什么它对第一个工作而对第二个不工作。我尝试过使用ConfigureAwait(false)
和TaskCreationOptions.RunContinuationsAsynchronously
,但都无济于事。
//App.xaml.cs
//App.xaml.cs
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
m_window.Activate();
_ = RunGame();
}
private async Task RunGame()
{
// ... create start view ...
var settings = await startView.ViewModel.WaitForStartAsync(); //This works
// ... create first game step ...
await currentStep.WaitForNextAsync(); //This doesn't
Exit(); //never called
}
//StartViewModel.cs
//StartViewModel.cs
public partial class StartViewModel : ObservableObject
{
//settings logic
private readonly TaskCompletionSource<GameSettings> m_taskCompletionSource = new();
[RelayCommand]
private void Start() => m_taskCompletionSource.TrySetResult(new(Difficulty));
public Task<GameSettings> WaitForStartAsync() => m_taskCompletionSource.Task;
}
//GameStepViewModel.cs
//GameStepViewModel.cs
public abstract partial class GameStepViewModel : ObservableObject
{
[ObservableProperty, NotifyCanExecuteChangedFor("NextCommand")]
private bool m_nextEnabled = true;
private readonly TaskCompletionSource _tcs = new();
[RelayCommand(CanExecute = "NextEnabled")]
private void Next() => _tcs.TrySetResult();
public Task WaitForNextAsync() => _tcs.Task;
}
<details>
<summary>英文:</summary>
Edit2: I tracked down the problem to an unrelated issue (the view and the logic were looking at different ViewModel instances. oops.)
I have the following code in a WinUI 3 application, using CommunityToolkit.MVVM, trying to use `TaskCompletionSource` with `RelayCommand` to control application flow around user input. It's working in one spot, and not in another. I cannot for the life of me tell what I'm doing differently in the second that would cause it to fail like this.
Edit: Following up on Andrew's answer, I've discovered it works fine if I call the Next method from code, rather than from the UI (using the generated NextCommand.) I still don't know why it works for the first one and not the second. I've tried using both `ConfigureAwait(false)` and `TaskCreationOptions.RunContinuationsAsynchronously`, to no avail.
``` lang-cs
//App.xaml.cs
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
m_window = new MainWindow();
m_window.Activate();
_ = RunGame();
}
private async Task RunGame()
{
// ... create start view ...
var settings = await startView.ViewModel.WaitForStartAsync(); //This works
// ... create first game step ...
await currentStep.WaitForNextAsync(); //This doesn't
Exit(); //never called
}
//StartViewModel.cs
public partial class StartViewModel : ObservableObject
{
//settings logic
private readonly TaskCompletionSource<GameSettings> m_taskCompletionSource = new();
[RelayCommand]
private void Start() => m_taskCompletionSource.TrySetResult(new(Difficulty));
public Task<GameSettings> WaitForStartAsync() => m_taskCompletionSource.Task;
}
//GameStepViewModel.cs
public abstract partial class GameStepViewModel : ObservableObject
{
[ObservableProperty, NotifyCanExecuteChangedFor("NextCommand")]
private bool m_nextEnabled = true;
private readonly TaskCompletionSource _tcs = new();
[RelayCommand(CanExecute = "NextEnabled")]
private void Next() => _tcs.TrySetResult();
public Task WaitForNextAsync() => _tcs.Task;
}
edit: I know from debugging that the Next()
method is being called successfully and the TaskCompletionSource.Task
is transitioning to RanToCompletion
.
答案1
得分: 2
这是对我有效的代码(在调用Next()之后调用Exit())。
App.xaml.cs
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
_window = new MainWindow();
_window.Activate();
_ = RunGame();
}
private async Task RunGame()
{
var startView = new StartView();
_window.Content = startView;
var settings = await startView.ViewModel.WaitForStartAsync();
var firstGameStep = new FirstGameStep();
firstGameStep.CallNextLater(TimeSpan.FromSeconds(5));
await firstGameStep.WaitForNextAsync();
Exit();
}
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;
namespace TaskCompletionSourceExample;
public enum Difficulty
{
Easy,
Medium,
Hard
}
public class GameSettings
{
public GameSettings(Difficulty difficulty)
{
}
}
public partial class StartViewModel : ObservableObject
{
private readonly TaskCompletionSource<GameSettings> m_taskCompletionSource = new();
private Difficulty Difficulty { get; set; }
public Task<GameSettings> WaitForStartAsync() => m_taskCompletionSource.Task;
[RelayCommand]
private void Start() => m_taskCompletionSource.TrySetResult(new GameSettings(Difficulty));
}
public sealed partial class StartView : Page
{
public StartView()
{
this.InitializeComponent();
}
public StartViewModel ViewModel { get; } = new();
}
public class FirstGameStep : GameStepViewModel
{
public FirstGameStep()
{
NextEnabled = false;
}
}
public abstract partial class GameStepViewModel : ObservableObject
{
public void CallNextLater(TimeSpan delay)
{
Task.Run(() =>
{
Task.Delay(delay);
NextEnabled = true;
Next();
});
}
[ObservableProperty, NotifyCanExecuteChangedFor("NextCommand")]
private bool m_nextEnabled = true;
private readonly TaskCompletionSource _tcs = new();
[RelayCommand(CanExecute = "NextEnabled")]
private void Next() => _tcs.TrySetResult();
public Task WaitForNextAsync() => _tcs.Task;
}
英文:
This is the code that worked (Exit() called after Next() is called) for me.
App.xaml.cs
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
_window = new MainWindow();
_window.Activate();
_ = RunGame();
}
private async Task RunGame()
{
var startView = new StartView();
_window.Content = startView;
var settings = await startView.ViewModel.WaitForStartAsync();
var firstGameStep = new FirstGameStep();
firstGameStep.CallNextLater(TimeSpan.FromSeconds(5));
await firstGameStep.WaitForNextAsync();
Exit();
}
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;
namespace TaskCompletionSourceExample;
public enum Difficulty
{
Easy,
Medium,
Hard
}
public class GameSettings
{
public GameSettings(Difficulty difficulty)
{
}
}
public partial class StartViewModel : ObservableObject
{
private readonly TaskCompletionSource<GameSettings> m_taskCompletionSource = new();
private Difficulty Difficulty { get; set; }
public Task<GameSettings> WaitForStartAsync() => m_taskCompletionSource.Task;
[RelayCommand]
private void Start() => m_taskCompletionSource.TrySetResult(new(Difficulty));
}
public sealed partial class StartView : Page
{
public StartView()
{
this.InitializeComponent();
}
public StartViewModel ViewModel { get; } = new();
}
public class FirstGameStep : GameStepViewModel
{
public FirstGameStep()
{
NextEnabled = false;
}
}
public abstract partial class GameStepViewModel : ObservableObject
{
public void CallNextLater(TimeSpan delay)
{
Task.Run(() =>
{
Task.Delay(delay);
NextEnabled = true;
Next();
});
}
[ObservableProperty, NotifyCanExecuteChangedFor("NextCommand")]
private bool m_nextEnabled = true;
private readonly TaskCompletionSource _tcs = new();
[RelayCommand(CanExecute = "NextEnabled")]
private void Next() => _tcs.TrySetResult();
public Task WaitForNextAsync() => _tcs.Task;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论