Tech Off Thread

9 posts

Forum Read Only

This forum has been made read only by the site admins. No new threads or comments can be added.

Parallel Tasks

Back to Forum: Tech Off
  • User profile image
    LotharV

    Hello

    I am trying to run two tasks simultaneously. One task can start and then has to wait for the other task to finish before it continues. This is how I tried to implement this.

    public void LoadData()
    {
          RequestData(Type.First);
          RequestData(Type.Second);
    }
    
    private async Task RequestData(Type type)
    {
          string[] sSplit;
          string s;
          HttpClient client = new HttpClient();
          switch (type)
          {
                case Type.First:
                      s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/ally.txt");
                      s = s.Replace("\n", ",");
                      sSplit = s.Split(',');
                      for (int i = 0; i < s.Length; i += 7)
                      {
                            Allies.Add(new Ally(sSplit[i], sSplit[i + 1], sSplit[i + 2], sSplit[i + 3], sSplit[i + 4], sSplit[i + 5], sSplit[i + 6], sSplit[i + 7]));
                      }
                      bAlly = true;
                      break;
                case Type.Second:
                      s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/player.txt");
                      s = s.Replace("\n", ",");
                      sSplit = s.Split(',');
                      while (!bAlly)
                      {
                            await Task.Delay(100);
                      }
                      for (int i = 0; i < s.Length; i += 7)
                      {
                            Players.Add(new Player(sSplit[i], sSplit[i + 1], sSplit[i + 2], sSplit[i + 3], sSplit[i + 4], sSplit[i + 5]));
                      }
                      bPlayer = true;
                      break;
                default:
                      break;
          }

    My problem is bAlly is never set to true.
    Can anyone tell me what I am doing wrong?

    Thanks in advance,
    Rahtol

     

     

  • User profile image
    MasterPi

    Where are you setting your breakpoint? It could be getting hit betore client.GetStringAsync(...) returns...

    Also, because LoadData(...) isn't waiting for the RequestData tasks, it's going to return before any of the Async calls complete (b/c that's what async does).

    Here's sort of the sequence of what's happening:

    LoadData(..)

    -> RequestData(Type.First);

            -> s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/ally.txt");

    -> RequestData(Type.Second);

            -> s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/player.txt");

    return from LoadData(...)

     

    To fix this, first notice that RequestData returns a Task object. You can choose to wait for the task to finish by calling Wait().

    So,

    var requestFirst = RequestData(Type.First);

    var requestSecond = RequestData(Type.Second);

    requestFirst.Wait();

    requestSecond.Wait();

  • User profile image
    LotharV

    Yes I know what you mean but that is not what i want to do.

    I'll add some comments to make it a bit more clear.

    case Type.First:
        //this can just run
                      s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/ally.txt");
                      s = s.Replace("\n", ",");
                      sSplit = s.Split(',');
                      for (int i = 0; i < s.Length; i += 7)
                      {
                            Allies.Add(new Ally(sSplit[i], sSplit[i + 1], sSplit[i + 2], sSplit[i + 3], sSplit[i + 4], sSplit[i + 5], sSplit[i + 6], sSplit[i + 7]));
                      }
                      bAlly = true;
                      break;
    case Type.Second:
    //this can just run
                      s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/player.txt");
                      s = s.Replace("\n", ",");
                      sSplit = s.Split(',');
    //this has to wait foor the first method to have finished. The above can run before the first method has finished.
                      while (!bAlly)
                      {
                            await Task.Delay(100);
                      }
                      for (int i = 0; i < s.Length; i += 7)
                      {
                            Players.Add(new Player(sSplit[i], sSplit[i + 1], sSplit[i + 2], sSplit[i + 3], sSplit[i + 4], sSplit[i + 5]));
                      }
                      bPlayer = true;
                      break;
    

    So the first method and the beginning of the second method can run simultaneous. The end of the second method has to wait for the first method to be finished.

     

     

  • User profile image
    wkempf

    @LotharV: You didn't understand what MasterPi told you. Awaiting for the tasks to complete is not going to cause a change in the behavior you want, as long as you write the code exactly as he has it. Both RequestData calls are made first, which means both are run simultaneously. Then you wait on both of them, to ensure both are completed before LoadData returns.

    Really, though, none of this is as you should code it. First, LoadData probably should be an asynchronous method itself. Generally you shouldn't block on asynchronous behavior. Then that whole bAlly hack is bad design as well. There's better solutions. The one I'd probably go with is to rely solely on continuations (await). Without providing the full code, here's roughly how this would look.

    public async Task LoadData()
    {
        var allies = GetAllies();
        var players = GetPlayers(allies);
        await Task.WhenAll(allies, players); // Actually, because GetPlayers awaits allies we could just await players here
    }
    
    public async Task GetAllies()
    {
       // Download and process the ally.txt
    }
    
    public async Task GetPlayers(Task<string[]> alliesTask)
    {
       // Download and split the player.txt
       var allies = await alliesTask;
       // Process the split players
    }

  • User profile image
    MasterPi

    @LotharV: That's a really confusing way to do it, and not a very well designed approach.

    await Task.Delay(100) is just silly because your logic of "end of second method has to wait for the first" is never guaranteed. Like, you could have just put Thread.Sleep() there instead and you would have gotten the same uncertain result...

    I think you don't really understand what await actually does. await brings the control back to the caller by returning a Task object. This way, the caller can continue to process code statements while the callee does its work asyncronously. When you care about the callee's actual result, you "await" the Task object, which will force the caller to sync up and wait for the callee to finish and return the final result.

    I suggest doing something similar to wkempf's code (or just do that completely). With continuations, especially, you'll be able to make guarantees about the sequence of operations.

  • User profile image
    wkempf

    @MasterPi: The Task.Delay(100) stuff does synchronize, because of the bAlly flag. It's a very poor substitute for a real synchronization object, but provided bAlly isn't used by anything else, there is a guarantee that the logic of "end of second method has to wait for the first". Techniques like this are known as a "busy wait", and they are generally very inefficient and are often the source of bugs, so it may well be that there's a bug in his code surrounding this that's causing him problems. I think it's far more likely, however, that he's just not understanding async/await and attempting to determine program state at some point in which these async methods simply haven't completed yet.

    Regardless, it is badly designed code.

  • User profile image
    MasterPi

    , wkempf wrote

    @MasterPi: The Task.Delay(100) stuff does synchronize, because of the bAlly flag.

    Oh, silly me. I completely skipped over the while loop. Now the code suddenly makes some sense...

    Yeah, in any case the issue still exists, LoadData() is just returning because it's not waiting for of its calls to finish.

  • User profile image
    shak3

    I have seen AutoResetEvent and ManualResetEvent classes to synchronize between multiple threads. Maybe they can be of some use

  • User profile image
    philjay

    this is another possible way :

    public async Task LoadData()
    {
        //load allies and players simultaneously (but don't add to list yet)
        var allies = LoadAllies();
        var players = LoadPlayers();
          
        //add allies then players (maybe do some computation first?)
        this.Allies.AddRange(await allies);
        this.Players.AddRange(await players);
    }
    
    private async Task<IEnumerable<Ally> LoadAllies()
    {
        var client = new HttpClient();
        var s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/ally.txt");
        return from line in s.Split("\n")
               let values = line.Split(',')
               select new Ally(values[0], values[1], values[2], values[3], values[4], values[5], values[6], values[7]);
    }
    
    private async Task<IEnumerable<Player>> LoadPlayers()
    {
        var client = new HttpClient();
        var s = await client.GetStringAsync("http://nl" + WorldName + ".tribalwars.nl/map/player.txt");
        return from line in s.Split("\n")
               let values = line.Split(',')
               select new Player(values[0], values[1], values[2], values[3], values[4], values[5]);
    }
    

Conversation locked

This conversation has been locked by the site admins. No new comments can be made.