简体   繁体   中英

C# - Make UI function during like a infinite loop until button pressed

i make a code in C# where i extract some records from an Access database , but i need the while going to the next iteration to depend on the click of a button. i tried with some Thread or Tasks , but it didn't worked because it blocked the UI which i need it to be seen and clickable.

Here's the code:

    bool nextClick = false ;

                while (readerSelect.Read())
                {

                    // show the correct panel
                    if (string.Compare(readerSelect[2].ToString(), "P1") == 0)
                    {
                        // panel with type 1
                        textBoxP1Text1.Text = readerSelect[3].ToString();
                        textBoxP1Text2.Text = readerSelect[4].ToString();
                        pictureBoxP1Image.ImageLocation = readerSelect[6].ToString();

                    }
                    else
                    {
                        // panel with type 2
                        textBoxP1Text2.Text = readerSelect[5].ToString();
                    }

    //this while need to be kind of infinite so the interation can't be processed and 
   //so when i need to change iteration i click the buttonNext 
                    while (!nextClick) {
                    startWhile:; 
                       MethodInvoker mi = delegate () {
                           if (nextClick)
                           {
                               Application.DoEvents(); 
                              // System.Windows.Forms.Application.Run();
                           }

                        };
                        this.Invoke(mi);
                        //break;
                        goto startWhile; 
                    }

 private void buttonNext_Click(object sender, EventArgs e)
    {
        // click on the next button
        nextClick = true; 
    }

You can use a semaphore within an async task, have the button Release it during each click, and have the while loop await it each time through. Here's a quick example, using a form that has a button1 and a label1 added to it:

public partial class Form1 : Form
{
    private readonly SemaphoreSlim signal = new SemaphoreSlim(0, int.MaxValue);

    public Form1()
    {
        this.InitializeComponent();
        this.RunLoop();
    }

    private async void RunLoop()
    {
        var i = 0;
        while (true)
        {
            this.label2.Text = $"Enqueued: {this.signal.CurrentCount}";
            await this.signal.WaitAsync(); // Wait button click async
            await Task.Delay(1000); // Simulate work
            this.label1.Text = $"Completed: {++i}";
        }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.signal.Release();
        this.label2.Text = $"Enqueued: {this.signal.CurrentCount + 1}";
        // Or if you want to limit the # people can queue up, then put this whole
        // thing in an `if (signal.CurrentCount < myLimit)` block, and optionally
        // disable the button once limit has been reached, and re-enable it right
        // before the `WaitAsync` call above.
    }
}

While Dax Fohl's answer works, it seems like you've got a problem in your design. I think you're violating the Single Responsibility Principle by having too much business logic going on in the Form class.

I'd recommend factoring out the business logic into its own class. Then rather than running through everything in a loop, you simply have the button click event process the next record and display the result. Here's an example of what I mean:

public partial class Form1 : Form
{
    private readonly DataProcessor dataProcessor = new DataProcessor();

    public Form1()
    {
        this.InitializeComponent();
    }

    private void button1Next_Click(object sender, EventArgs e)
    {
        this.buttonNext.Enabled = false;
        this.ProcessNext();
    }

    private async void ProcessNext()
    {
        string s = await this.dataProcessor.ProcessNext();
        this.textBoxP1Text1.Text = s;
        this.buttonNext.Enabled = true;
    }
}

public class DataProcessor
{
    private readonly Random r = new Random(); // Or reader or whatever.

    public async Task<string> ProcessNext() // Just using `string` as an example.
    {
        await Task.Delay(1000);
        return this.r.Next().ToString();
    }
}

I think this will be easier to understand and more maintainable in the future. When a new team member looks at semaphore stuff (or your future self), it'll be hard to understand/remember what the point of all that was. Here, you just have a local function that does one thing and is easy to follow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM