在RelayCommand中设置TaskCompletionSource的结果不会触发等待的代码。

huangapple go评论98阅读模式
英文:

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,尝试在用户输入周围使用TaskCompletionSourceRelayCommand来控制应用程序流程。它在一个地方工作正常,而在另一个地方则不工作。

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&#39;s working in one spot, and not in another. I cannot for the life of me tell what I&#39;m doing differently in the second that would cause it to fail like this.

Edit: Following up on Andrew&#39;s answer, I&#39;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&#39;t know why it works for the first one and not the second. I&#39;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&#39;t

    Exit(); //never called
}

//StartViewModel.cs
public partial class StartViewModel : ObservableObject
{
    //settings logic

    private readonly TaskCompletionSource&lt;GameSettings&gt; m_taskCompletionSource = new();

    [RelayCommand]
    private void Start() =&gt; m_taskCompletionSource.TrySetResult(new(Difficulty));

    public Task&lt;GameSettings&gt; WaitForStartAsync() =&gt; m_taskCompletionSource.Task;
}

//GameStepViewModel.cs
public abstract partial class GameStepViewModel : ObservableObject
{
    [ObservableProperty, NotifyCanExecuteChangedFor(&quot;NextCommand&quot;)]
    private bool m_nextEnabled = true;

    private readonly TaskCompletionSource _tcs = new();

    [RelayCommand(CanExecute = &quot;NextEnabled&quot;)]
    private void Next() =&gt; _tcs.TrySetResult();

    public Task WaitForNextAsync() =&gt; _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&lt;GameSettings&gt; m_taskCompletionSource = new();

    private Difficulty Difficulty { get; set; }

    public Task&lt;GameSettings&gt; WaitForStartAsync() =&gt; m_taskCompletionSource.Task;

    [RelayCommand]
    private void Start() =&gt; 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(() =&gt;
        {
            Task.Delay(delay);
            NextEnabled = true;
            Next();
        });
    }

    [ObservableProperty, NotifyCanExecuteChangedFor(&quot;NextCommand&quot;)]
    private bool m_nextEnabled = true;

    private readonly TaskCompletionSource _tcs = new();

    [RelayCommand(CanExecute = &quot;NextEnabled&quot;)]
    private void Next() =&gt; _tcs.TrySetResult();

    public Task WaitForNextAsync() =&gt; _tcs.Task;
}

huangapple
  • 本文由 发表于 2023年8月5日 07:05:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/76839531.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定