等待用户从后台选择

Wait for user to choose from the backend

我正在编写软件的后端部分,有时我需要我的用户选择一些东西,这可能是一个复杂的过程,用户甚至可以随时取消选择。

从后端我想做类似的事情:

private async void StartAction()
{
    //some code
    var SelectedItem = await UI.RequestUserToChooseItem();
    // some final code using the selected item
}

这里我不知道如何处理取消,但我可以发送 null 并假设如果 SelectedItem 为 null 它已被取消。

但是 UI 部分呢?当用户选择事物时,如何处理 return 调用?

我需要在这里执行一些步骤:(这是伪代码,我什至不知道从哪里开始)

public List<Item> RequestUserToChooseItem()
{
    PrepareItemsInList();
    ShowSelectionPanel();
    List<Items> SelectedItemsFromPanel = WaitForUserToChose(); //???????
    return SelectedItemsFromPanel;

}

然后我们有取消按钮:

private void CancelButtonClicked(object sender, EventArgs e)
{
    CancelWaitedSelectionProcessAndReturnNull(); //????
}

您可以使用 TaskCompletionSource 来表示选择。像

private TaskCompletionSource<MyOptions> tcs;
public Task<MyOptions> ShowPanelAndWaitForSelection(){
    // show panel and do other initialization
    tcs = new TaskCompletionSource<MyOptions>();
    return  tcs.Task;
}
public void OnOptionSelection(MyOptions value) => tcs.SetResult(value);
public void OnCanceled() => tcs.SetCanceled();

当任务被取消时,任何等待者都会得到一个 OperationCanceledException,因此您的代码通常类似于:

try{
    ...
    var selectedOption = await ShowPanelAndWaitForSelection();
    ...
}
catch(OperationCanceledException){
    // Handle cancellation
}
catch(Exception e){
   // Handle actual errors
}

这假设您的 UI 是 non-modal,就像以相同的形式显示和隐藏面板一样。如果您对每个步骤都使用模态对话框,则不需要任何异步代码。

这种风格本质上是利用编译器生成状态机,而不是手工编写这样的状态机。我认为这可能是一种处理特定情况的有用样式,因为您可以使用 if/while 等常规结构来制作决策树。但这可能并不总是绝对积极的,并且可能会绊倒不这样做的开发人员敬请期待

这是一个异步方法,它异步等待第一次点击一个或多个按钮,并且returns被点击的按钮:

public static Task<Button> OnClickAsync(params Button[] buttons)
{
    var tcs = new TaskCompletionSource<Button>();
    foreach (var button in buttons) button.Click += OnClick;
    return tcs.Task;

    void OnClick(object sender, RoutedEventArgs e)
    {
        foreach (var button in buttons) button.Click -= OnClick;
        tcs.SetResult((Button)sender);
    }
}

可以这样使用:

public async Task<List<Item>> RequestUserToChooseItemAsync()
{
    PrepareItemsInList();
    ShowSelectionPanel();
    var selectedButton = await OnClickAsync(btnOK, btnCancel);
    if (selectedButton == btnCancel) return null;
    return SelectedItemsFromPanel;
}

此方法应该在 UI 线程上专门调用,而不是在后台线程上调用,因为它与 UI 元素交互。