Home > Software engineering >  Why are my asynchron functions not running really asynchronously (in paralell) in my Blazor Server a
Why are my asynchron functions not running really asynchronously (in paralell) in my Blazor Server a

Time:07-11

I am normaly a senior PLC Programmer, but also now programming a Blazor server app. I know it is shamefull to post this question, after lot of questions about asynchron programming (async-await) in C# have been posted. It is also Ok for me if you close this question due to this reason. But I have really read tens of posts in stackoverflow, and in the rest of the internet but still cannot understand how async / await is working. It is always explained that this is the easiest method for asynchron programming. But my old brain cannot understand. I thought, perhaps a patient developer can help me nevertheless and explaine me the mechanism with very easy words.

What I want to do is quite simple: I have a page in my Blazor server app, where the user is selecting a Machine from a dropdown list. After selection is done, he has to click to the "Connect" button. When this button is pressed

a) The dropdown list shall be deactivated immediatly (This part I have programmed as first)

b) a simple ping test shall be carried out, in order to know whether machine is available or not.(This part I have programmed as second.)

--> At the end a ping status text shall be displayed on screen.

Of course I want that these two actions shall be done asynchronously. But after days of lots of trials, I have the shamefull status that, always the ping is carried out first and the program is blocked for 3-4 seconds when the machine is not reachable (no ping answer), and finally the dropdown box is being deactivated always afterwards.

I know that PLC programming and C# are quite different worlds. Either I should really give up the C# programming and think about retirement with my PLC history. Or someone should explain me what is wrong in my code and correct my code if possible. I have really red as I said lots of entries but, each time I get more and more confused. This is my code:

@page "/connect"
@using System.IO
@using System.Net.NetworkInformation;

<select value="@selectedString_MAE"  disabled="@IsDisabled" @onchange="func_MAE">
<option value="">-- Select MAE --</option>
@foreach (var mae in templates_MAE)
{
    <option value=@mae>@mae</option>
}
</select>
<button  @onclick="@(() => { paralell_tasks(); })">Connect</button>
<p>Ping: @Ping_Win_status</p>
<p>Ping: @Ping_Win_response</p>

@code {

public static bool IsDisabled { get; set; }

public static async void paralell_tasks()
{       
    Disable_DropDown(); // Why is that part not processes as first ??? Please explain me??
    await Ping_Test_Win(); //Why is this part blocking my code always when machine is offline??
}

public static void Disable_DropDown()
{
    IsDisabled = true;
}

public static async Task Ping_Test_Win()
{
        Ping_Win_response = "";
        Ping_Win_status = "";
        try
        {
            Ping Ping_Win = new Ping();              
            PingReply Reply_win = Ping_Win.Send(Connect2.MAE_IP_Win, 4000);
            if (Reply_win != null)
            {
                Ping_Win_response = "Win "   Reply_win.Status   "   Time(ms) : "   Reply_win.RoundtripTime.ToString()   "   Address : "   Reply_win.Address;
                Ping_Win_status = Reply_win.Status.ToString();

                if (Ping_Win_status.Contains("Success"))
                {
                    //Some code
                }
            }
            if (Reply_win == null)
            {
                Ping_Win_response = "Win Null TimedOut";
                Ping_Win_status = "";
            }
        catch (Exception ex)
        {
            // Error logging code
        }
     }
  }
}

CodePudding user response:

A function being async doesn't make it asynchronous if it doesn't await for anything to yield control to other code. (It might be worth to (re-)read the async/await concept guide.)

To make that function actually async, you would need to use Ping.PingAsync and then wait until the callback happens. (I should note PingAsync isn't async in the async/await (TAP) sense, but in the sense that it doesn't block and causes a callback to fire later, so you can't just await on it, but you could wrap it in a Task of your own.)

If you need to run a synchronous function asynchronously, you could run it in a different thread, using e.g. the built-in thread pool stuff.

CodePudding user response:

I think you're under the misconception that when you call Disbable_Dropdown a render event will take place. It doesn't because the whole code block is synchronous and the Renderer, which actually does the re-render only gets some thread time once parelell_tasks had completed. So the ping finishes and finally the UI gets updated.

Here's an all in one page that demonstrates how to do what I think you are expecting. "environment.gov.ck" is a long way away down a lot of wire so relatively slow and shows the pings happening.

@page "/"
@using System.Text
@using System.IO
@using System.Net.NetworkInformation;

<h3>Ping</h3>

<select  value="@ipAddress" disabled="@this.DisableSelect" @onchange=IpAddressSelected>
    <option value="">-- Select a Machine --</option>
    <option value="environment.gov.ck">environment.gov.ck</option>
    <option value="www.coldelm.com">www.coldelm.com</option>
    <option value="www.microsoft.com">www.microsoft.com</option>
</select>

<div >
    <button  disabled="@this.DisableButton" @onclick=this.PingAddress>Ping @ipAddress</button>
</div>

<div>
    <pre>
        @this.log.ToString()
    </pre>
</div>

@code {
    private StringBuilder log = new StringBuilder();
    private string ipAddress = string.Empty;
    private bool DisableSelect = false;
    private bool DisableButton = true;

    private void IpAddressSelected(ChangeEventArgs e)
    {
        if (e.Value is not null)
        {
            this.ipAddress = e.Value?.ToString() ?? string.Empty;
            this.DisableSelect = true;
            this.DisableButton = false;
        }
    }

    private async Task PingAddress()
    {
        if (this.ipAddress != string.Empty)
            await PingMachine();
    }

    private async ValueTask PingMachine()
    {
        log.AppendLine($"Pinging {this.ipAddress}");
        // StateHasChanged will get run by UI event Handler on the first await, 
        // after that we need to call it manually
        var timesToPing = 4;
        var counter = 1;
        while (counter <= timesToPing)
        {
            var reply = await Pinger(this.ipAddress);
            log.AppendLine($"Pinged {this.ipAddress} {counter} times time:{reply.RoundtripTime} status: {reply.Status.ToString()}");
            counter  ;
            StateHasChanged();
        }

        this.DisableButton = true;
        this.ipAddress = string.Empty;
        this.DisableSelect = false;
        // StateHasChanged will get run by UI event Handler
    }

    private async ValueTask<PingReply> Pinger(string ipAddress)
    {
        var ping = new Ping();
        var reply = await ping.SendPingAsync(ipAddress);
        return reply;
    }
}

CodePudding user response:

I can fix the first issue for you.
First, simplify the calling code

  ... @onclick="paralell_tasks">

then make paralell_tasks() actually async for UI purposes:

//public static async void paralell_tasks()
  private async Task paralell_tasks()
  {       
    Disable_DropDown();   // Q: Why is that part not processes as first ??? 
    // A: it is processed first but not yet rendered
    await Task.Delay(1);  // render the updates so far
    await Ping_Test_Win(); //Why is this part blocking my code always when machine is offline??
  }

that leaves //Why is this part blocking my code always when machine is offline??

And the answer is that your Ping_Test_Win() may be async but it is not asynchronous, it does not await anywhere. Rewrite it based on PingAsync() as @AKX points out.

  • Related