0

I'm looking to listen to the result of a task(s), or timeout if the none are made or completed. Specifically, I'm using Playwright and am adding an event listener for requests and want to timeout if none of those requests include what I'm looking for (or even get created). Example code below:

I've tried looking into WhenAny, but that requires knowing the tasks ahead of time. Also looked into Monitor.TryEnter, but not sure how to have the requests acquire the lock before they're generated? Maybe a semaphore or something?

string? result = null;

// Start listening for new pages (ClickAsync below opens in a new page)
context.Page += (_, newPage) =>
{
  // From the new page, start listening for requests that page generates
  newPage.Request += async (_, request) =>
  {
    if (request.Url.Contains("some_identifier"))
    {
      await newPage.GotoAsync(request.Url);
      result = await newPage.InnerTextAsync("body");
      // Do something here? We now have the result we want, stop waiting and return it!
    }
  };
};

await mainPage.Locator("a#somelink").ClickAsync();

// Wait for *up to* 30 seconds here.

return result;
hardkoded
  • 18,915
  • 3
  • 52
  • 64
Uxonith
  • 1,602
  • 1
  • 13
  • 16
  • 1
    `tried looking into WhenAny, but that requires knowing the tasks ahead of time` - you can call `WhenAny` on the tasks you already have, which creates a single Task for you to wait on. If you later have more tasks, call another `WhenAny` on those new tasks and the Task resulted from the previous call to `WhenAny`... – GSerg Jun 12 '22 at 19:22
  • 1
    Does the `GotoAsync` method support passing a `CancellationToken` argument? – Theodor Zoulias Jun 12 '22 at 20:28
  • 2
    Did you see [RunAndWaitForRequestAsync](https://playwright.dev/dotnet/docs/api/class-page#page-wait-for-request)? – hardkoded Jun 13 '22 at 13:16
  • 1
    or have one of the tasks passed into `WhenAny` be a "task list has changed" future (it's quite annoying that `Task` doesn't support persistent reusable synchronization objects the way `WaitHandle` does, which leaves you facing a race condition at the moment the Task linked to the recurring event needs to be swapped out) – Ben Voigt Jun 13 '22 at 14:00
  • @hardkoded I'll definitely have to look into RunAndWaitForPageAsync and RunAndWaitForRequestAsync. Not sure why I ended up not using those. I experimented with them early on, but ended up switching to the event listeners instead. I still may end up using the code in my answer to have an "overall timeout" since I have to listen for pages and then requests. – Uxonith Jun 13 '22 at 18:51

1 Answers1

0

Using ideas from comments on my answer I was able to accomplish the goal. I ended up passing a cancellation token to a Task.Delay(30000, token). Then if I got the data I wanted and set the result value, I just cancelled the token.

string? result = null;
var tokenSource = new CancellationTokenSource();
var cancelToken = tokenSource.Token;

// Start listening for new pages (ClickAsync below opens in a new page)
context.Page += (_, newPage) =>
{
  // From the new page, start listening for requests that page generates
  newPage.Request += async (_, request) =>
  {
    if (request.Url.Contains("some_identifier"))
    {
      await newPage.GotoAsync(request.Url);
      result = await newPage.InnerTextAsync("body");
      tokenSource.Cancel(); // <-- Got what we needed, cancel 30 second timer
    }
  };
};

await mainPage.Locator("a#somelink").ClickAsync();

try {
  await Task.Delay(30000, cancelToken); // <-- Timeout after 30 seconds.
}
catch
{
  // Not a fan of tokenSource.Cancel() throwing an exception, but I can deal with it.
}

return result;
Uxonith
  • 1,602
  • 1
  • 13
  • 16