วิธีการบล็อกการไหลของรหัสจนกว่าเหตุการณ์จะเริ่มทำงานใน C #


11

ที่นี่เรามีด้วยGrid Buttonเมื่อผู้ใช้คลิกปุ่มวิธีการในคลาสยูทิลิตี้จะถูกดำเนินการซึ่งบังคับให้แอปพลิเคชันได้รับการคลิกบนกริด Gridการไหลของรหัสต้องหยุดที่นี่และไม่ได้ดำเนินต่อไปจนกว่าผู้ใช้มีการคลิกที่

ฉันมีคำถามที่คล้ายกันมาก่อนที่นี่:

รอจนกระทั่งผู้ใช้คลิก C # WPF

ในคำถามนั้นฉันได้รับคำตอบโดยใช้ async / คอยซึ่งใช้งานได้ แต่เนื่องจากฉันจะใช้เป็นส่วนหนึ่งของ API ฉันไม่ต้องการใช้ async / waitit เนื่องจากผู้บริโภคจะต้องทำเครื่องหมายวิธีการด้วย async ซึ่งฉันไม่ต้องการ

ฉันจะเขียนUtility.PickPoint(Grid grid)วิธีเพื่อให้บรรลุเป้าหมายนี้ได้อย่างไร

ฉันเห็นสิ่งนี้ซึ่งอาจช่วยได้ แต่ไม่เข้าใจอย่างเต็มที่ที่จะสมัครที่นี่เพื่อความซื่อสัตย์:

การบล็อกจนกว่าเหตุการณ์จะเสร็จสิ้น

พิจารณาว่าเป็นสิ่งที่ต้องการวิธี Console.ReadKey () ในแอปพลิเคชันคอนโซล เมื่อเราเรียกใช้เมธอดนี้โฟลว์โค้ดจะหยุดจนกว่าเราจะป้อนค่าบางค่า โปรแกรมดีบั๊กไม่ทำงานต่อไปจนกว่าเราจะป้อนข้อมูล ฉันต้องการพฤติกรรมที่แน่นอนสำหรับวิธี PickPoint () การไหลของรหัสจะหยุดจนกว่าผู้ใช้คลิกบนกริด

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid x:Name="View" Background="Green"/>
        <Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        var point = Utility.PickPoint(View);


        MessageBox.Show(point.ToString());
    }
}

public static class Utility
{
    public static Point PickPoint(Grid grid)
    {

    }
}

วิธีที่ชัดเจนคือAync/Awaitวิธีการเกี่ยวกับการดำเนินการ A และการบันทึกการดำเนินการของรัฐในขณะนี้คุณต้องการให้ผู้ใช้คลิกกริด .. ดังนั้นหากผู้ใช้คลิกกริดคุณตรวจสอบสถานะถ้าเป็นจริงจากนั้นดำเนินการอื่น
Rao Hammas Hussain

@RaoHammasHussain ฉันได้อัปเดตคำถามของฉันพร้อมลิงก์ที่อาจช่วยได้ วิธีการยูทิลิตี้จะเป็นส่วนหนึ่งของ API ที่ผู้ใช้ของ API จะเรียกเมื่อใดก็ตามที่เขาต้องการที่จะขอให้ผู้ใช้คลิกบนหน้าจอ พิจารณาเหมือนหน้าต่างแจ้งสำหรับข้อความในแอปพลิเคชัน windows ปกติหรือวิธี Console.Readline () ในกรณีเหล่านี้การไหลของรหัสหยุดจนกว่าผู้ใช้จะป้อนบางสิ่ง ตอนนี้ฉันต้องการสิ่งที่แน่นอน แต่คราวนี้ผู้ใช้คลิกบนหน้าจอ
Vahid

AutoResetEventไม่ใช่สิ่งที่คุณต้องการ?
Rao Hammas Hussain

@RaoHammasHussain ฉันคิดอย่างนั้น แต่จริงๆไม่รู้วิธีใช้ที่นี่
Vahid

มันเหมือนกับว่าคุณกำลังใช้ WAIT STATE อย่างตั้งใจ มันจำเป็นจริงๆเหรอ? เพราะคุณไม่สามารถใส่มันvar point = Utility.PickPoint(Grid grid);ในวิธีการคลิกกริด? ดำเนินการบางอย่างและส่งคืนการตอบกลับหรือไม่
Rao Hammas Hussain

คำตอบ:


9

"จะบล็อกการไหลของรหัสได้อย่างไรจนกว่าเหตุการณ์จะเริ่มทำงาน"

วิธีการของคุณผิด การขับเคลื่อนเหตุการณ์ไม่ได้หมายถึงการบล็อกและรอเหตุการณ์ คุณไม่เคยรออย่างน้อยคุณพยายามอย่างหนักเพื่อหลีกเลี่ยง การรอคอยเป็นการสิ้นเปลืองทรัพยากรบล็อกเธรดและอาจแนะนำความเสี่ยงของการหยุดชะงักหรือเธรดซอมบี้ (ในกรณีที่ไม่มีการเพิ่มสัญญาณปล่อย)
ควรชัดเจนว่าการบล็อกเธรดเพื่อรอเหตุการณ์เป็นรูปแบบการต่อต้านเนื่องจากขัดแย้งกับแนวคิดของเหตุการณ์

โดยทั่วไปคุณมีสองตัวเลือก (ทันสมัย): ใช้ API แบบอะซิงโครนัสหรือ API ที่ขับเคลื่อนด้วยเหตุการณ์ เนื่องจากคุณไม่ต้องการใช้ API แบบอะซิงโครนัสคุณจึงเหลือ API ที่ขับเคลื่อนด้วยเหตุการณ์

กุญแจสำคัญของ API ที่ขับเคลื่อนด้วยเหตุการณ์คือแทนที่จะบังคับให้ผู้โทรรอผลลัพธ์หรือแบบสำรวจความคิดเห็นเพื่อให้ผลลัพธ์คุณให้ผู้โทรดำเนินการต่อและส่งการแจ้งเตือนเมื่อผลลัพธ์พร้อมหรือการดำเนินการเสร็จสิ้น ในขณะเดียวกันผู้เรียกสามารถดำเนินการอื่น ๆ

เมื่อมองปัญหาจากมุมมองของเธรดเธรด API ที่ขับเคลื่อนด้วยเหตุการณ์จะอนุญาตให้เธรดการโทรเช่นเธรด UI ซึ่งดำเนินการตัวจัดการเหตุการณ์ของปุ่มให้เป็นอิสระในการจัดการต่อไปเช่นการดำเนินการอื่น ๆ ที่เกี่ยวข้องกับ UI เช่นการแสดงองค์ประกอบ UI หรือจัดการอินพุตของผู้ใช้เช่นการเคลื่อนไหวของเมาส์และการกดปุ่ม API ที่ขับเคลื่อนด้วยเหตุการณ์มีเอฟเฟกต์หรือเป้าหมายเหมือน API แบบอะซิงโครนัสแม้ว่าจะสะดวกน้อยกว่ามาก

เนื่องจากคุณไม่ได้ให้รายละเอียดที่เพียงพอเกี่ยวกับสิ่งที่คุณพยายามทำสิ่งที่Utility.PickPoint()ทำอยู่จริงและผลลัพธ์ของงานคืออะไรหรือเหตุใดผู้ใช้ต้องคลิกที่ `กริดฉันจึงไม่สามารถเสนอทางออกที่ดีกว่าให้คุณได้ . ฉันสามารถเสนอรูปแบบทั่วไปของวิธีการใช้ความต้องการของคุณ

โฟลว์หรือเป้าหมายของคุณแบ่งออกเป็นสองขั้นตอนอย่างชัดเจนเพื่อให้เป็นลำดับของการปฏิบัติงาน:

  1. ดำเนินการการทำงาน 1 เมื่อผู้ใช้คลิกปุ่ม
  2. ดำเนินการการดำเนินการ 2 (ดำเนินการต่อ / ดำเนินการจนเสร็จสิ้น 1) เมื่อผู้ใช้คลิกที่ Grid

มีข้อ จำกัด อย่างน้อยสองข้อ:

  1. ทางเลือก: ลำดับจะต้องเสร็จสิ้นก่อนที่ไคลเอ็นต์ API จะได้รับอนุญาตให้ทำซ้ำได้ ลำดับเสร็จสมบูรณ์เมื่อการดำเนินงาน 2 เสร็จสิ้นลง
  2. การดำเนินการ 1 จะถูกดำเนินการเสมอก่อนการดำเนินการ 2 การดำเนินการ 1 เริ่มต้นลำดับ
  3. การดำเนินการ 1 จะต้องเสร็จสิ้นก่อนที่ไคลเอ็นต์ API จะได้รับอนุญาตให้ดำเนินการ 2

สิ่งนี้ต้องการการแจ้งเตือน (เหตุการณ์) สองรายการสำหรับลูกค้าของ API เพื่ออนุญาตการไม่โต้ตอบ:

  1. การดำเนินการ 1 เสร็จสมบูรณ์ (หรือจำเป็นต้องมีการโต้ตอบ)
  2. การดำเนินการ 2 (หรือเป้าหมาย) เสร็จสมบูรณ์

คุณควรปล่อยให้ API ของคุณใช้พฤติกรรมและข้อ จำกัด นี้โดยการเปิดเผยวิธีสาธารณะสองวิธีและกิจกรรมสาธารณะสองเหตุการณ์

เนื่องจากการใช้งานนี้อนุญาตให้เรียก API (ไม่เกิดขึ้นพร้อมกัน) หนึ่งครั้งเท่านั้นจึงขอแนะนำให้เปิดเผยIsBusyคุณสมบัติเพื่อระบุลำดับการทำงาน การทำเช่นนี้ช่วยให้การสำรวจสถานะปัจจุบันก่อนเริ่มลำดับใหม่แม้ว่าจะแนะนำให้รอให้เหตุการณ์ที่เสร็จสิ้นแล้วเพื่อเรียกใช้งานตามลำดับ

Implement / refactor Utility API

Utility.cs

class Utility
{
  public event EventHandler InitializePickPointCompleted;
  public event EventHandler<PickPointCompletedEventArgs> PickPointCompleted;
  public bool IsBusy { get; set; }
  private bool IsPickPointInitialized { get; set; }

  // The prefix 'Begin' signals the caller or client of the API, 
  // that he also has to end the sequence explicitly
  public void BeginPickPoint(param)
  {
    // Implement constraint 1
    if (this.IsBusy)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint is already executing. Call EndPickPoint before starting another sequence.");
    }

    // Set the flag that a current sequence is in progress
    this.IsBusy = true;

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => StartOperationNonBlocking(param));
  }

  public void EndPickPoint(param)
  {
    // Implement constraint 2 and 3
    if (!this.IsPickPointInitialized)
    {
      // Alternatively just return or use Try-do pattern
      throw new InvalidOperationException("BeginPickPoint must have completed execution before calling EndPickPoint.");
    }

    // Execute operation until caller interaction is required.
    // Execute in background thread to allow API caller to proceed with execution.
    Task.Run(() => CompleteOperationNonBlocking(param));
  }

  private void StartOperationNonBlocking(param)
  {
    ... // Do something

    // Flag the completion of the first step of the sequence (to guarantee constraint 2)
    this.IsPickPointInitialized = true;

    // Request caller interaction to kick off EndPickPoint() execution
    OnInitializePickPointCompleted();
  }

  private void CompleteOperationNonBlocking(param)
  {
    // Execute goal and get the result of the completed task
    Point result = ExecuteGoal();

    // Reset API sequence (allow next client invocation)
    this.IsBusy = false;
    this.IsPickPointInitialized = false;

    // Notify caller that execution has completed and the result is available
    OnPickPointCompleted(result);
  }

  private void OnInitializePickPointCompleted()
  {
    // Set the result of the task
    this.InitializePickPointCompleted?.Invoke(this, EventArgs.Empty);
  }

  private void OnPickPointCompleted(Point result)
  {
    // Set the result of the task
    this.PickPointCompleted?.Invoke(this, new PickPointCompletedEventArgs(result));
  }
}

PickPointCompletedEventArgs.cs

class PickPointCompletedEventArgs : AsyncCompletedEventArgs 
{
  public Point Result { get; }

  public PickPointCompletedEventArgs(Point result)
  {
    this.Result = result;
  }
}

ใช้ API

MainWindow.xaml.cs

partial class MainWindow : Window
{
  private Utility Api { get; set; }

  public MainWindow()
  {
    InitializeComponent();

    this.Api = new Utility();
  }

  private void StartPickPoint_OnButtonClick(object sender, RoutedEventArgs e)
  {
    this.Api.InitializePickPointCompleted += RequestUserInput_OnInitializePickPointCompleted;

    // Invoke API and continue to do something until the first step has completed.
    // This is possible because the API will execute the operation on a background thread.
    this.Api.BeginPickPoint();
  }

  private void RequestUserInput_OnInitializePickPointCompleted(object sender, EventArgs e)
  {
    // Cleanup
    this.Api.InitializePickPointCompleted -= RequestUserInput_OnInitializePickPointCompleted;

    // Communicate to the UI user that you are waiting for him to click on the screen
    // e.g. by showing a Popup, dimming the screen or showing a dialog.
    // Once the input is received the input event handler will invoke the API to complete the goal   
    MessageBox.Show("Please click the screen");  
  }

  private void FinishPickPoint_OnGridMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
  {
    this.Api.PickPointCompleted += ShowPoint_OnPickPointCompleted;

    // Invoke API to complete the goal
    // and continue to do something until the last step has completed
    this.Api.EndPickPoint();
  }

  private void ShowPoint_OnPickPointCompleted(object sender, PickPointCompletedEventArgs e)
  {
    // Cleanup
    this.Api.PickPointCompleted -= ShowPoint_OnPickPointCompleted;

    // Get the result from the PickPointCompletedEventArgs instance
    Point point = e.Result;

    // Handle the result
    MessageBox.Show(point.ToString());
  }
}

MainWindow.xaml

<Window>
  <Grid MouseLeftButtonUp="FinishPickPoint_OnGridMouseLeftButtonUp">
    <Button Click="StartPickPoint_OnButtonClick" />
  </Grid>
</Window>

หมายเหตุ

กิจกรรมที่เพิ่มขึ้นในเธรดพื้นหลังจะดำเนินการจัดการของพวกเขาในหัวข้อเดียวกัน การเข้าถึงDispatcherObjectอิลีเมนต์ UI จากตัวจัดการซึ่งถูกเรียกใช้งานบนเธรดเบื้องหลังต้องการการดำเนินการที่สำคัญเพื่อจัดคิวการDispatcherใช้Dispatcher.InvokeหรือDispatcher.InvokeAsyncเพื่อหลีกเลี่ยงข้อยกเว้นข้ามเธรด
อ่านคำพูดเกี่ยวกับDispatcherObjectเพื่อเรียนรู้เพิ่มเติมเกี่ยวกับปรากฏการณ์นี้ที่เรียกว่าความสัมพันธ์ดิสแพตเชอร์หรือความสัมพันธ์ของเธรด
เพื่อความสะดวกในการใช้ API ฉันขอแนะนำให้จัดกิจกรรมทั้งหมดตามบริบทดั้งเดิมของผู้โทรด้วยการจับภาพและใช้ผู้โทรSynchronizationContextหรือโดยใช้AsyncOperation(หรือAsyncOperationManager)

ตัวอย่างข้างต้นสามารถปรับปรุงได้อย่างง่ายดายโดยการยกเลิก (แนะนำ) เช่นโดยการเปิดเผยCancel()วิธีเช่นPickPointCancel()และการรายงานความคืบหน้า (ควรใช้Progress<T>)


ความคิด - ตอบความคิดเห็นของคุณ

เนื่องจากคุณกำลังมาหาฉันเพื่อหาทางออกการบล็อกที่ "ดีกว่า" ให้ฉันเป็นตัวอย่างของแอปพลิเคชันคอนโซลฉันรู้สึกว่าคุณเชื่อว่าการรับรู้หรือมุมมองของคุณผิดทั้งหมด

ลองพิจารณาแอปพลิเคชั่นคอนโซลพร้อมโค้ดสองบรรทัดในตัว

var str = Console.ReadLine(); 
Console.WriteLine(str);

จะเกิดอะไรขึ้นเมื่อคุณเรียกใช้แอปพลิเคชันในโหมดแก้ไขข้อบกพร่อง มันจะหยุดที่บรรทัดแรกของรหัสและบังคับให้คุณป้อนค่าใน Console UI จากนั้นหลังจากที่คุณป้อนบางสิ่งและกด Enter มันจะดำเนินการบรรทัดถัดไปและพิมพ์สิ่งที่คุณป้อนจริง ๆ ฉันคิดเกี่ยวกับพฤติกรรมเดียวกันทั้งหมด แต่ในแอปพลิเคชัน WPF "

แอปพลิเคชันคอนโซลเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง แนวคิดของเธรดแตกต่างกันเล็กน้อย แอปพลิเคชันคอนโซลไม่มี GUI เพียงแค่อินพุต / เอาต์พุต / สตรีมข้อผิดพลาด คุณไม่สามารถเปรียบเทียบสถาปัตยกรรมของแอปพลิเคชันคอนโซลกับแอปพลิเคชัน GUI ที่มีรูปแบบต่างกันได้ สิ่งนี้ใช้ไม่ได้ คุณต้องเข้าใจและยอมรับสิ่งนี้จริงๆ

นอกจากนี้ยังไม่ได้รับการหลอกโดยลักษณะ คุณรู้หรือไม่ว่าเกิดอะไรขึ้นภายใน Console.ReadLine ? มีการใช้งานอย่างไร? มันบล็อกเธรดหลักหรือไม่และจะอ่านอินพุตแบบขนานหรือไม่ หรือว่าเป็นแค่การสำรวจความคิดเห็น?
นี่คือการดำเนินการเดิมของConsole.ReadLine :

public virtual String ReadLine() 
{
  StringBuilder sb = new StringBuilder();
  while (true) 
  {
    int ch = Read();
    if (ch == -1) 
      break;
    if (ch == '\r' || ch == '\n') 
    {
      if (ch == '\r' && Peek() == '\n') 
        Read();
      return sb.ToString();
    }
    sb.Append((char)ch);
  }
  if (sb.Length > 0) 
    return sb.ToString();
  return null;
}

อย่างที่คุณเห็นมันเป็นเรื่องง่าย ซิงโครการดำเนินงาน มันสำรวจความคิดเห็นของผู้ใช้ในการวนซ้ำ "ไม่มีที่สิ้นสุด" ไม่มีบล็อกเวทมนต์และดำเนินการต่อ

WPF สร้างขึ้นรอบ ๆ เธรดการแสดงผลและเธรด UI เธรดเหล่านั้นหมุนอยู่เสมอเพื่อสื่อสารกับระบบปฏิบัติการเช่นการจัดการอินพุตของผู้ใช้ - การเก็บแอปพลิเคชันตอบสนองได้ คุณไม่ต้องการหยุด / บล็อกเธรดนี้เนื่องจากจะทำให้เฟรมเวิร์กไม่ทำงานพื้นหลังที่สำคัญเช่นการตอบสนองต่อเหตุการณ์เมาส์ - คุณไม่ต้องการให้เมาส์ค้าง:

กำลังรอ = การบล็อกเธรด = ไม่ตอบสนอง = ไม่ดี UX = ผู้ใช้ / ลูกค้ารำคาญ = ปัญหาในสำนักงาน

บางครั้งโฟลว์แอปพลิเคชันจำเป็นต้องรออินพุตหรือรูทีนเพื่อให้สมบูรณ์ แต่เราไม่ต้องการบล็อกเธรดหลัก
นั่นเป็นสาเหตุที่ผู้คนคิดค้นรูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสที่ซับซ้อนเพื่อรอโดยไม่ปิดกั้นเธรดหลักและไม่บังคับให้นักพัฒนาเขียนรหัสมัลติเธรดที่ซับซ้อนและผิดพลาด

เฟรมเวิร์กแอปพลิเคชันที่ทันสมัยทุกข้อเสนอการดำเนินการแบบอะซิงโครนัสหรือรูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสเพื่อให้การพัฒนาโค้ดง่ายและมีประสิทธิภาพ

ความจริงที่ว่าคุณกำลังพยายามอย่างหนักที่จะต่อต้านรูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสแสดงให้เห็นว่าฉันขาดความเข้าใจ นักพัฒนาสมัยใหม่ทุกคนชอบ API แบบอะซิงโครนัสมากกว่าแบบซิงโครนัส ไม่มีนักพัฒนาที่จริงจังที่จะใช้awaitคำหลักหรือประกาศวิธีการของเขาasyncคำหลักหรือจะประกาศวิธีการของเขาไม่มีใคร คุณเป็นคนแรกที่ฉันพบผู้ที่บ่นเกี่ยวกับ API แบบอะซิงโครนัสและผู้ที่พบว่าพวกเขาไม่สะดวกที่จะใช้

ถ้าฉันจะตรวจสอบเฟรมเวิร์กของคุณซึ่งมีเป้าหมายเพื่อแก้ไขปัญหาที่เกี่ยวข้องกับ UI หรือทำให้งานที่เกี่ยวข้องกับ UI นั้นง่ายขึ้นฉันก็คาดหวังว่ามันจะเป็นแบบอะซิงโครนัส - ไปตลอดทาง
API ที่เกี่ยวข้องกับ UI ซึ่งไม่อะซิงโครนัสจะสูญเปล่าเนื่องจากจะทำให้รูปแบบการเขียนโปรแกรมของฉันซับซ้อนยิ่งขึ้นดังนั้นโค้ดของฉันซึ่งมีแนวโน้มที่จะเกิดข้อผิดพลาดได้ง่ายขึ้นและยากต่อการดูแลรักษา

มุมมองที่แตกต่าง: เมื่อคุณยอมรับว่าการรอคอยบล็อกเธรด UI กำลังสร้างประสบการณ์การใช้งานที่ไม่ดีและไม่พึงประสงค์เนื่องจาก UI จะหยุดจนกว่าการรอจะสิ้นสุดลงเมื่อคุณตระหนักถึงสิ่งนี้แล้วทำไมคุณถึงเสนอ API หรือปลั๊กอิน สนับสนุนให้นักพัฒนาทำสิ่งนี้ - ใช้การรอ?
คุณไม่ทราบว่าปลั๊กอินของบุคคลที่สามจะทำอะไรและจะใช้เวลานานเท่าใดในการรูทีนจนกว่าจะเสร็จสมบูรณ์ นี่เป็นเพียงการออกแบบ API ที่ไม่ดี เมื่อ API ของคุณทำงานบนเธรด UI ผู้โทรของ API ของคุณจะต้องสามารถโทรออกโดยไม่บล็อกได้

หากคุณปฏิเสธโซลูชันราคาถูกหรือสง่างามเพียงอย่างเดียวให้ใช้วิธีการจัดกิจกรรมตามที่แสดงในตัวอย่างของฉัน
มันทำในสิ่งที่คุณต้องการ: เริ่มกิจวัตรประจำวัน - รอการป้อนข้อมูลของผู้ใช้ - ดำเนินการต่อ - บรรลุเป้าหมาย

ฉันพยายามหลายครั้งเพื่ออธิบายว่าทำไมการรอ / บล็อกจึงเป็นการออกแบบแอปพลิเคชันที่ไม่ดี อีกครั้งคุณไม่สามารถเปรียบเทียบ UI ของคอนโซลกับ UI แบบกราฟิกที่หลากหลายเช่นการจัดการอินพุตอย่างเดียวมีความหลากหลายมากกว่าที่ซับซ้อนเพียงแค่รับฟังอินพุตสตรีม ฉันไม่รู้ระดับประสบการณ์ของคุณและตำแหน่งที่คุณเริ่ม แต่คุณควรเริ่มต้นที่จะยอมรับรูปแบบการเขียนโปรแกรมแบบอะซิงโครนัส ฉันไม่รู้เหตุผลที่คุณพยายามหลีกเลี่ยง แต่มันไม่ฉลาดเลย

รูปแบบการเขียนโปรแกรมแบบอะซิงโครนัสวันนี้มีการใช้งานทุกที่ในทุกแพลตฟอร์มคอมไพเลอร์ทุกสภาพแวดล้อมเบราว์เซอร์เซิร์ฟเวอร์เดสก์ท็อปฐานข้อมูล - ทุกที่ โมเดลที่ขับเคลื่อนด้วยเหตุการณ์อนุญาตให้บรรลุเป้าหมายเดียวกัน แต่จะสะดวกกว่าในการใช้ (สมัคร / ยกเลิกการเป็นสมาชิก / จากเหตุการณ์อ่านเอกสาร (เมื่อมีเอกสาร) เพื่อเรียนรู้เกี่ยวกับเหตุการณ์) โดยอาศัยเธรดพื้นหลัง Event-driven เป็นแบบเก่าและควรใช้เฉพาะเมื่อไลบรารีแบบอะซิงโครนัสไม่พร้อมใช้งานหรือไม่สามารถใช้ได้

จากหมายเหตุด้านข้าง:. Framwork .NET (. NET Standard) นำเสนอTaskCompletionSource(ท่ามกลางวัตถุประสงค์อื่น ๆ ) เพื่อให้วิธีง่ายๆในการแปลง API ที่มีอยู่เดิมที่มีอยู่ให้เป็น API แบบอะซิงโครนัส

"ฉันเห็นพฤติกรรมที่แน่นอนใน Autodesk Revit"

พฤติกรรม (สิ่งที่คุณสัมผัสหรือสังเกตเห็น) แตกต่างจากการใช้งานประสบการณ์นี้มาก สองสิ่งที่แตกต่าง Autodesk ของคุณน่าจะใช้ไลบรารีแบบอะซิงโครนัสหรือคุณสมบัติภาษาหรือกลไกการเธรดอื่น ๆ และยังเกี่ยวข้องกับบริบทด้วย เมื่อวิธีที่อยู่ในใจของคุณกำลังดำเนินการบนเธรดพื้นหลังนักพัฒนาอาจเลือกที่จะบล็อกเธรดนี้ เขามีเหตุผลที่ดีที่จะทำสิ่งนี้หรือเพียงแค่เลือกการออกแบบที่ไม่ดี คุณอยู่ในเส้นทางที่ไม่ถูกต้อง;) การบล็อกไม่ดี
(ซอร์สรหัส Autodesk เป็นโอเพ่นซอร์สหรือไม่หรือคุณรู้ได้อย่างไรว่ามีการนำไปใช้อย่างไร)

ฉันไม่ต้องการที่จะรุกรานคุณโปรดเชื่อฉัน แต่โปรดพิจารณาใหม่เพื่อใช้ API แบบอะซิงโครนัสของคุณ มีอยู่ในหัวของคุณเท่านั้นที่นักพัฒนาซอฟต์แวร์ไม่ต้องการใช้ async / คอย เห็นได้ชัดว่าคุณมีความคิดที่ผิด และลืมเกี่ยวกับอาร์กิวเมนต์แอปพลิเคชันคอนโซลนั่น - มันไร้สาระ;)

API ที่เกี่ยวข้องกับ UI ต้อง ใช้ async / รอเมื่อเป็นไปได้ มิฉะนั้นคุณจะปล่อยให้งานทั้งหมดเขียนโค้ดที่ไม่บล็อกไปยังลูกค้าของ API ของคุณ คุณจะบังคับให้ฉันตัดทุกการเรียก API ของคุณลงในเธรดพื้นหลัง หรือใช้การจัดการเหตุการณ์ที่สะดวกสบายน้อยลง เชื่อฉัน - นักพัฒนาทุกคนต่างตกแต่งสมาชิกของเขาasyncมากกว่าทำกิจกรรมจัดการ ทุกครั้งที่คุณใช้เหตุการณ์คุณอาจเสี่ยงต่อการรั่วไหลของหน่วยความจำ - ขึ้นอยู่กับสถานการณ์บางอย่าง แต่ความเสี่ยงนั้นเป็นจริงและไม่เกิดขึ้นยากเมื่อตั้งโปรแกรม

ฉันหวังว่าคุณจะเข้าใจว่าเพราะเหตุใดการบล็อกจึงไม่ดี ฉันหวังว่าคุณจะตัดสินใจใช้ async / รอการเขียน API แบบอะซิงโครนัสที่ทันสมัย อย่างไรก็ตามฉันแสดงให้คุณเห็นวิธีที่ธรรมดามากในการรอการไม่บล็อกการใช้กิจกรรมแม้ว่าฉันขอให้คุณใช้ async / คอย

"API จะช่วยให้โปรแกรมเมอร์สามารถเข้าถึง UI และอื่น ๆ ได้ตอนนี้สมมติว่าโปรแกรมเมอร์ต้องการพัฒนา Add-in ที่เมื่อคลิกปุ่มผู้ใช้ขั้นสุดท้ายจะถูกขอให้เลือกจุดใน UI"

หากคุณไม่ต้องการอนุญาตให้ปลั๊กอินเข้าถึงองค์ประกอบ UI ได้โดยตรงคุณควรจัดให้มีอินเทอร์เฟซสำหรับมอบหมายกิจกรรมหรือเปิดเผยส่วนประกอบภายในผ่านวัตถุที่เป็นนามธรรม
API ภายในจะสมัครสมาชิกกิจกรรม UI ในนามของ Add-in จากนั้นมอบหมายกิจกรรมโดยการเปิดเผยเหตุการณ์ "wrapper" ที่สอดคล้องกับไคลเอ็นต์ API API ของคุณต้องมีตะขอบางอย่างที่ Add-in สามารถเชื่อมต่อเพื่อเข้าถึงส่วนประกอบแอปพลิเคชันที่เฉพาะเจาะจง API ปลั๊กอินทำหน้าที่เหมือนอะแดปเตอร์หรือซุ้มเพื่อให้เข้าถึงภายนอกจากภายใน
เพื่อให้ระดับความเหงา

ดูที่ Visual Studio จัดการปลั๊กอินหรือช่วยให้เราสามารถใช้งานได้ แกล้งทำเป็นว่าคุณต้องการเขียนปลั๊กอินสำหรับ Visual Studio และทำวิจัยเกี่ยวกับวิธีการทำเช่นนี้ คุณจะรู้ว่า Visual Studio แสดงถึง internals ผ่านทางอินเตอร์เฟสหรือ API EG คุณสามารถจัดการแก้ไขรหัสหรือได้รับข้อมูลเกี่ยวกับเนื้อหาของบรรณาธิการโดยไม่จริงการเข้าถึงมัน


สวัสดีขอขอบคุณที่เข้ามาใกล้คำถามจากมุมมองอื่น ขออภัยหากคำถามนั้นค่อนข้างคลุมเครือในรายละเอียด พิจารณาแอปพลิเคชั่นคอนโซลด้วยโค้ดสองบรรทัดนี้ var str = Console.ReadLine(); Console.WriteLine(str);จะเกิดอะไรขึ้นเมื่อคุณรันแอปพลิเคชันในโหมดแก้ไขข้อบกพร่อง มันจะหยุดที่บรรทัดแรกของรหัสและบังคับให้คุณป้อนค่าใน Console UI จากนั้นหลังจากที่คุณป้อนบางสิ่งและกด Enter มันจะดำเนินการบรรทัดถัดไปและพิมพ์สิ่งที่คุณป้อนจริง ๆ ฉันคิดเกี่ยวกับพฤติกรรมเดียวกัน แต่ในแอปพลิเคชัน WPF
Vahid

ในแอปพลิเคชัน CAD ที่ฉันกำลังพัฒนาผู้ใช้ควรจะสามารถขยายได้โดยการเพิ่ม / ปลั๊กอิน API จะช่วยให้โปรแกรมเมอร์สามารถเข้าถึง UI และอื่น ๆ ได้ตอนนี้สมมติว่าโปรแกรมเมอร์ต้องการที่จะพัฒนา Addin ว่าเมื่อคลิกปุ่มใด ๆ ผู้ใช้ขั้นสุดท้ายจะถูกขอให้เลือกจุดใน UI จากนั้นโค้ดจะทำสิ่งอื่น ๆ สิ่งที่ยอดเยี่ยมกับจุดที่กำหนด บางทีพวกเขาจะขอจุดอื่นที่จะเลือกและวาดเส้น ฯลฯ
Vahid

ฉันเห็นพฤติกรรมที่แน่นอนใน Autodesk Revit
Vahid

1
ฉันมีสิ่งที่จะพูดเกี่ยวกับความต้องการของคุณ โปรดอ่านคำตอบที่อัปเดตของฉัน ฉันโพสต์คำตอบที่นั่นเพราะมันใช้เวลานานกว่านั้น ฉันยอมรับว่าคุณเรียกฉัน โปรดในขณะที่การอ่านโปรดจำไว้ว่าฉันไม่ต้องการที่จะรุกรานคุณ
BionicCode

ขอบคุณสำหรับคำตอบที่อัพเดต แน่นอนฉันไม่โกรธเคือง ในทางตรงกันข้ามฉันขอบคุณจริง ๆ สำหรับเวลาและความพยายามที่คุณได้วางไว้บนนี้
Vahid

5

โดยส่วนตัวแล้วฉันคิดว่านี่เป็นสิ่งที่ซับซ้อนเกินไปสำหรับทุกคน แต่บางทีฉันอาจไม่เข้าใจอย่างถ่องแท้ว่าทำไมเรื่องนี้ถึงต้องทำอย่างใดอย่างหนึ่ง แต่ดูเหมือนว่าสามารถใช้การตรวจสอบบูลอย่างง่ายได้ที่นี่

ก่อนอื่นให้สร้างตารางของคุณที่สามารถทดสอบได้ด้วยการตั้งค่า BackgroundและIsHitTestVisibleคุณสมบัติมิฉะนั้นจะไม่แม้แต่จะจับการคลิกเมาส์

<grid MouseLeftButtonUp="Grid_MouseLeftButtonUp" IsHitTestVisible="True" Background="Transparent">

ถัดไปสร้างค่าบูลที่สามารถเก็บได้ว่าจะเกิดเหตุการณ์ "GridClick" หรือไม่ เมื่อมีการคลิกกริดให้ตรวจสอบค่านั้นและดำเนินการจากเหตุการณ์คลิกกริดหากกำลังรอการคลิก

ตัวอย่าง:

bool awaitingClick = false;


private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
   awaitingClick=true;
}

private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{     
     //Stop here if the program shouldn't do anything when grid is clicked
     if (!awaitingClick) { return; } 

     //Run event
     var point = Utility.PickPoint(View);
     MessageBox.Show(point.ToString());

     awaitingClick=false;//Reset
}

สวัสดี Tronald ฉันคิดว่าคุณเข้าใจผิดคำถาม สิ่งที่ฉันต้องการคือรหัสที่จะหยุดที่ Utility.PickPoint (ดู) และดำเนินการต่อหลังจากผู้ใช้คลิกบนกริด
Vahid

อ๋อฉันเข้าใจผิดโดยสิ้นเชิง ขอโทษฉันไม่ได้ตระหนักว่าคุณต้องการทุกสิ่งที่จะหยุดจริง ฉันคิดว่าเป็นไปไม่ได้หากไม่มีมัลติเธรดเนื่องจาก UI ทั้งหมดจะถูกบล็อก
Tronald

ฉันยังไม่แน่ใจว่ามันเป็นไปไม่ได้ เป็นไปได้อย่างแน่นอนกับ async / await ซึ่งไม่ใช่โซลูชันแบบมัลติเธรด แต่สิ่งที่ฉันต้องการคือทางเลือกสำหรับโซลูชัน async / await
Vahid

1
แน่นอน แต่คุณพูดว่าคุณไม่สามารถใช้ async / await ดูเหมือนว่าคุณจะต้องใช้โปรแกรมเลือกจ่ายงานและเธรดที่แยกออกจากเธรดหลัก (ซึ่งเรียกใช้งานบน UI) ฉันหวังว่าคุณจะพบวิธีอื่นในขณะที่ฉันสนใจตัวเอง
Tronald

2

ฉันพยายามเล็ก ๆ น้อย ๆ async/awaitแต่ผมไม่สามารถที่จะทำให้มันได้โดยไม่ต้อง เพราะถ้าเราไม่ได้ใช้มันเป็นสาเหตุDeadLockหรือ UI ถูกปิดกั้นจากนั้นเราก็สามารถเปิดใช้งานGrid_Clickได้

private async void ToolBtn_OnClick(object sender, RoutedEventArgs e)
{
    var senderBtn = sender as Button;
    senderBtn.IsEnabled = false;

    var response = await Utility.PickPoint(myGrid);
    MessageBox.Show(response.ToString());
    senderBtn.IsEnabled = true;
}  

public static class Utility
{
    private static TaskCompletionSource<bool> tcs;
    private static Point _point = new Point();

    public static async Task<Point> PickPoint(Grid grid)
    {
        tcs = new TaskCompletionSource<bool>();
        _point = new Point();

        grid.MouseLeftButtonUp += GridOnMouseLeftButtonUp;


        await tcs.Task;

        grid.MouseLeftButtonUp -= GridOnMouseLeftButtonUp;
        return _point;
    }


    private static void GridOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        // do something here ....
        _point = new Point { X = 23, Y = 34 };
        // do something here ....

        tcs.SetResult(true); // as soon its set it will go back

    }
}

@ ขอบคุณนี่เป็นคำตอบเดียวกับที่ฉันได้รับสำหรับคำถามอื่นที่ใช้ async / คอย
Vahid

โอ้ใช่ ! ฉันสังเกตเห็นตอนนี้ แต่ฉันคิดว่ามันเป็นวิธีเดียวที่ฉันพบว่าใช้งานได้
Rao Hammas Hussain

2

คุณสามารถบล็อกแบบอะซิงโครนัสโดยใช้SemaphoreSlim:

public partial class MainWindow : Window, IDisposable
{
    private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(0, 1);

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var point = Utility.PickPoint(View);

        // do not continue the code flow until the user has clicked on the grid. 
        // so when we debug, the code flow will literally stop here.
        await _semaphoreSlim.WaitAsync();

        MessageBox.Show(point.ToString());
    }

    private void View_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //click on grid detected....
        _semaphoreSlim.Release();
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        Dispose();
    }

    public void Dispose() => _semaphoreSlim.Dispose();
}

คุณไม่สามารถและไม่ต้องการปิดกั้นเธรดโปรแกรมเลือกจ่ายแบบซิงโครนัสเนื่องจากมันจะไม่สามารถจัดการคลิกGridได้เช่นไม่สามารถบล็อกและจัดการเหตุการณ์ได้ในเวลาเดียวกัน


ขอบคุณสำหรับคำตอบอื่น ฉันสงสัยว่ามันจะทำอย่างไรสำหรับตัวอย่างใน Console.Readline ()? เมื่อคุณไปถึงวิธีนี้ในการดีบักเกอร์มันจะหยุดอย่างน่าอัศจรรย์ถ้าเราป้อนบางอย่าง มันแตกต่างกันโดยพื้นฐานในแอปพลิเคชั่นคอนโซลหรือไม่? เราไม่สามารถมีพฤติกรรมเหมือนกันในแอปพลิเคชัน WinForms / WPF หรือไม่? ฉันเคยเห็นสิ่งนี้ใน API ของ Autodesk Revit มีวิธีการ PickPoint () ที่บังคับให้คุณเลือกจุดบนหน้าจอและฉันไม่เคยเห็น async / ที่รอใช้! อย่างน้อยเป็นไปได้ที่จะซ่อนคำหลักที่รอคอยและเรียกมันอย่างใดจากวิธีการซิงค์หรือไม่?
Vahid

@Vahid: Console.Readline บล็อกคือไม่ส่งคืนจนกว่าจะมีการอ่านบรรทัด PickPointวิธีการของคุณไม่ได้ มันกลับมาทันที อาจบล็อกได้ แต่จากนั้นคุณจะไม่สามารถจัดการกับการป้อนข้อมูล UI ในระหว่างที่ฉันเขียนไว้ในคำตอบของฉัน กล่าวอีกนัยหนึ่งคุณจะต้องจัดการกับการคลิกภายในวิธีการเพื่อให้ได้พฤติกรรมเดียวกัน
mm8

Console.Realine () บล็อก แต่ในเวลาเดียวกันเหตุการณ์ KeyPress ได้รับอนุญาต เรามีพฤติกรรมแบบเดียวกันนี้ไม่ได้เหรอ? ปิดกั้นโดย PickPoint () และอนุญาตเฉพาะ MouseEvents เท่านั้น ฉันไม่สามารถเข้าใจได้ว่าทำไมมันจึงเป็นไปได้ใน Console แต่ไม่ได้อยู่ในแอปพลิเคชันที่ใช้ UI
Vahid

จากนั้นคุณต้องตั้งค่าโปรแกรมเลือกจ่ายงานแยกต่างหากในการPickPointจัดการกิจกรรมเมาส์ ฉันไม่เห็นว่าคุณจะไปกับเรื่องนี้?
mm8

1
@Vahind: ทำโค้ด async และให้ผู้ใช้รอวิธีการหรือไม่ นี่คือ API ที่ฉันคาดหวังในฐานะนักพัฒนา UI การเรียกวิธีการบล็อกในแอปพลิเคชัน UI นั้นไม่สมเหตุสมผล
mm8

2

ในทางเทคนิคเป็นไปได้ทั้งแบบมีAutoResetEventหรือไม่มีasync/awaitก็ได้ แต่มีข้อเสียเปรียบที่สำคัญ:

public static Point PickPoint(Grid grid)
{
    var pointPicked = new AutoResetEvent(false);
    grid.MouseLeftButtonUp += (s, e) => 
    {
        // do whatever after the grid is clicked

        // signal the end of waiting
        pointPicked.Set();
    };

    // code flow will stop here and wait until the grid is clicked
    pointPicked.WaitOne();
    // return something...
}

ข้อเสียเปรียบ: ถ้าคุณเรียกใช้วิธีนี้โดยตรงในตัวจัดการเหตุการณ์ปุ่มตามที่โค้ดตัวอย่างของคุณทำไว้การหยุดชะงักจะเกิดขึ้นและคุณจะเห็นว่าแอปพลิเคชันหยุดตอบสนอง เนื่องจากคุณใช้เธรด UI เพียงอย่างเดียวในการรอการคลิกของผู้ใช้จึงไม่สามารถตอบสนองต่อการกระทำของผู้ใช้รวมถึงการคลิกของผู้ใช้บนกริด

ผู้บริโภคของวิธีการที่ควรจะเรียกใช้ในหัวข้ออื่นเพื่อป้องกันการหยุดชะงัก หากสามารถรับประกันได้ว่าเป็นเรื่องปกติ มิฉะนั้นคุณจะต้องเรียกใช้เมธอดดังนี้:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    // here I used ThreadPool, but you may use other means to run on another thread
    ThreadPool.QueueUserWorkItem(new WaitCallback(Capture));
}

private void Capture(object state)
{
    // do not continue the code flow until the user has clicked on the grid. 
    // so when we debug, the code flow will literally stop here.
    var point = Utility.PickPoint(View);


    MessageBox.Show(point.ToString());
}

มันอาจทำให้เกิดปัญหากับผู้บริโภคของ API ของคุณมากขึ้นยกเว้นพวกเขาเคยจัดการเธรดของตนเอง นั่นเป็นเหตุผลที่async/awaitคิดค้น


ขอบคุณเคนเป็นไปได้ไหมที่ addin เริ่มต้นจากเธรดอื่นจากนั้นกิจกรรมของมันจะไม่บล็อกเธรด UI หลักหรือไม่
Vahid

@Vahid ใช่และไม่ใช่ ใช่คุณสามารถเรียกวิธีการบล็อกในเธรดอื่นและตัดมันในวิธีอื่นได้ อย่างไรก็ตามเมธอด wrapper ยังคงต้องถูกเรียกในเธรดอื่นนอกเหนือจากเธรด UI เพื่อหลีกเลี่ยงการบล็อก UI เนื่องจาก wrapper จะบล็อกเธรดการโทรหากมีการซิงโครนัส แม้ว่า wrapper ภายในจะบล็อกเธรดอื่น แต่ก็ยังต้องรอผลลัพธ์และบล็อกการเรียกเธรด หากผู้เรียกใช้เมธอด wrapper ในเธรด UI UI จะถูกบล็อก
เคนฮุง

0

ฉันคิดว่าปัญหาอยู่ที่การออกแบบเอง หาก API ของคุณทำงานกับองค์ประกอบเฉพาะควรใช้ในตัวจัดการเหตุการณ์ขององค์ประกอบนี้ไม่ใช่ในองค์ประกอบอื่น

ตัวอย่างเช่นที่นี่เราต้องการรับตำแหน่งของเหตุการณ์คลิกบนกริด API ต้องใช้ในตัวจัดการเหตุการณ์ที่เกี่ยวข้องกับเหตุการณ์ในองค์ประกอบกริดไม่ใช่องค์ประกอบของปุ่ม

ตอนนี้ถ้าความต้องการคือการจัดการคลิกบนกริดหลังจากที่เราคลิกปุ่มแล้วความรับผิดชอบของปุ่มจะเพิ่มตัวจัดการเหตุการณ์บนกริดและเหตุการณ์คลิกบนกริดจะแสดงกล่องข้อความและลบ ตัวจัดการเหตุการณ์นี้ถูกเพิ่มโดยปุ่มเพื่อไม่ให้เกิดการเรียกใช้อีกต่อไปหลังจากคลิกนี้ ... (ไม่จำเป็นต้องบล็อก UI Thread)

เพื่อบอกว่าถ้าคุณบล็อกเธรด UI ในการคลิกปุ่มฉันไม่คิดว่าเธรด UI จะสามารถทริกเกอร์เหตุการณ์การคลิกบนกริดได้ในภายหลัง


0

ก่อนอื่นเลยเธรด UI ไม่สามารถบล็อกได้เหมือนคำตอบที่คุณได้จากคำถามแรก
หากคุณสามารถเห็นด้วยกับสิ่งนั้นหลีกเลี่ยง async / คอยให้ลูกค้าของคุณทำการปรับเปลี่ยนน้อยลงสามารถทำได้และยังไม่จำเป็นต้องมีหลายเธรด

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        Utility.PickPoint(View, (x) => MessageBox.Show(x.ToString()));
    }
}

public static class Utility
{
    private static Action<Point> work;

    public static void PickPoint(Grid grid, Action<Point> work)
    {
        if (Utility.work == null)
        {
            grid.PreviewMouseLeftButtonUp += Grid_PreviewMouseLeftButtonUp;
            Utility.work = work;
        }
    }

    private static void Grid_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        var grid = (Grid)sender;
        work.Invoke(e.GetPosition(grid));
        grid.PreviewMouseLeftButtonUp -= Grid_PreviewMouseLeftButtonUp;
        Utility.work = null;
    }
}   

แต่ถ้าคุณต้องการปิดกั้นเธรด UI หรือ "การไหลของรหัส" คำตอบจะเป็นไปไม่ได้ เนื่องจากถ้าเธรด UI ถูกบล็อกจึงไม่สามารถรับอินพุตเพิ่มเติมได้อีก
ตั้งแต่ที่คุณพูดถึงเกี่ยวกับแอพคอนโซลฉันแค่ทำคำอธิบายง่ายๆ
เมื่อคุณเรียกใช้แอปคอนโซลหรือการโทรAllocConsoleจากกระบวนการที่ไม่ได้แนบกับคอนโซล (หน้าต่าง) ใด ๆ conhost.exe ซึ่งสามารถให้คอนโซล (หน้าต่าง) จะถูกดำเนินการและแอปคอนโซลหรือกระบวนการโทรจะแนบกับคอนโซล ( หน้าต่าง).
ดังนั้นรหัสใด ๆ ที่คุณเขียนที่สามารถบล็อกเธรดผู้โทรเช่นConsole.ReadKeyจะไม่บล็อกเธรด UI ของหน้าต่างคอนโซลใด ๆ นั่นเป็นเหตุผลว่าทำไมเมื่อแอปคอนโซลรอการป้อนข้อมูลของคุณ แต่ยังสามารถตอบสนองต่ออินพุตอื่นเช่นการคลิกเมาส์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.