Facepunch / Facepunch.Steamworks

Another fucking c# Steamworks implementation
MIT License
2.87k stars 344 forks source link

SteamMatchmaking.LobbyList.RequestAsync return completed task just once #610

Open imkoi opened 2 years ago

imkoi commented 2 years ago

Describe the bug If we don`t use async callbacks - SteamMatchmaking.LobbyList.RequestAsync will be completed just first time when we call it. If we try to make lobby refresh through this api - await of task will be infinity and never return completed task.

To Reproduce Steps to reproduce the behavior:

  1. Initialize SteamClient with asyncCallbacks == false
  2. Call SteamClient.RunCallbacks() in update method
  3. await SteamMatchmaking.LobbyList.RequestAsync() 5 times
  4. some some of Request will never be finished

Calling Code

private async void Awake()
{
     SteamClient.Init(appId, false);

     ExecuteCallbacksAsync(cancellationToken).RunAsyncExtension(Debug.LogException);

    var result1 = await GetMatchListAsync(cancellationToken);
    var result2 = await GetMatchListAsync(cancellationToken);
    var result3 = await GetMatchListAsync(cancellationToken);
    var result4 = await GetMatchListAsync(cancellationToken);
    var result5 = await GetMatchListAsync(cancellationToken);
}

public Task<Lobby[]> GetMatchListAsync(CancellationToken cancellationToken)
        {
            var taskCompletionSource = new TaskCompletionSource<Lobby[]>();

            using (cancellationToken.Register(OnFailed))
            {
                var task = SteamMatchmaking.LobbyList.RequestAsync()
                    .ContinueWith(resultTask =>
                    {
                        var lobbies = resultTask.Result;

                        if (lobbies != null)
                        {
                            taskCompletionSource.TrySetResult(lobbies);
                        }
                    }, cancellationToken);

                task.RunAsynchronously(exception =>
                {
                    taskCompletionSource.TrySetException(exception);
                });
            }

            void OnFailed()
            {
                taskCompletionSource.TrySetCanceled();
            }

            return taskCompletionSource.Task;
        }

private async Task ExecuteCallbacksAsync(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
#if !UNITY_EDITOR
                    if (SteamClient.RestartAppIfNecessary(_appId))
                    {
                        Application.Quit();
                    }
#endif

                                        SteamClient.RunCallbacks();
                }
                catch(Exception exception)
                {
                    Debug.LogException(exception);
                }

                await Task.Yield();
            }
        }

Expected behavior Every await of SteamMatchmaking.LobbyList.RequestAsync() will be finished with result or exception

Desktop (please complete the following information):

mastef commented 2 years ago

In the ContinueWith you may need to add TaskScheduler.FromCurrentSynchronizationContext() as second param to stay on the main thread.

Currently if your ContinueWith ends up on another thread, then unity won't get those changes in the original Awake and it will look like infinite loading - since the answer never arrives. Your failure rate may be coincidental with amount of threads you have available.

var task = SteamMatchmaking.LobbyList.RequestAsync()
    .ContinueWith(resultTask =>
    {
        // ...
    }, cancellationToken, TaskScheduler.FromCurrentSynchronizationContext());

We had to do something similar with the Workshop Editor.SubmitAsync calls.

Daahrien commented 2 years ago

@imkoi Did you find a fix? (weird this doesnt happen in my other project, I'm using same facepunch.Steamworks version in both, the latest release one).

Edit.-Nvm. I wasnt calling the callbacks manually in this project :p