mirror of
https://github.com/ppy/osu.git
synced 2025-03-06 05:12:55 +08:00
Make threading even more thread safe
This commit is contained in:
parent
8c3b0a3167
commit
085115cba5
@ -21,9 +21,9 @@ namespace osu.Game.Extensions
|
|||||||
/// Whether errors should be logged as errors visible to users, or as debug messages.
|
/// Whether errors should be logged as errors visible to users, or as debug messages.
|
||||||
/// Logging as debug will essentially silence the errors on non-release builds.
|
/// Logging as debug will essentially silence the errors on non-release builds.
|
||||||
/// </param>
|
/// </param>
|
||||||
public static void CatchUnobservedExceptions(this Task task, bool logAsError = false)
|
public static Task CatchUnobservedExceptions(this Task task, bool logAsError = false)
|
||||||
{
|
{
|
||||||
task.ContinueWith(t =>
|
return task.ContinueWith(t =>
|
||||||
{
|
{
|
||||||
Exception? exception = t.Exception?.AsSingular();
|
Exception? exception = t.Exception?.AsSingular();
|
||||||
if (logAsError)
|
if (logAsError)
|
||||||
|
@ -110,37 +110,49 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly TaskChain joinOrLeaveTaskChain = new TaskChain();
|
private readonly TaskChain joinOrLeaveTaskChain = new TaskChain();
|
||||||
|
private CancellationTokenSource? joinCancellationSource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="room">The API <see cref="Room"/>.</param>
|
/// <param name="room">The API <see cref="Room"/>.</param>
|
||||||
public async Task JoinRoom(Room room) => await joinOrLeaveTaskChain.Add(async () =>
|
public async Task JoinRoom(Room room)
|
||||||
{
|
{
|
||||||
if (Room != null)
|
var cancellationSource = new CancellationTokenSource();
|
||||||
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
|
|
||||||
|
|
||||||
Debug.Assert(room.RoomID.Value != null);
|
|
||||||
|
|
||||||
// Join the server-side room.
|
|
||||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value);
|
|
||||||
Debug.Assert(joinedRoom != null);
|
|
||||||
|
|
||||||
// Populate users.
|
|
||||||
Debug.Assert(joinedRoom.Users != null);
|
|
||||||
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser));
|
|
||||||
|
|
||||||
// Update the stored room (must be done on update thread for thread-safety).
|
|
||||||
await scheduleAsync(() =>
|
await scheduleAsync(() =>
|
||||||
{
|
{
|
||||||
Room = joinedRoom;
|
joinCancellationSource?.Cancel();
|
||||||
apiRoom = room;
|
joinCancellationSource = cancellationSource;
|
||||||
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
}, CancellationToken.None);
|
||||||
});
|
|
||||||
|
|
||||||
// Update room settings.
|
await joinOrLeaveTaskChain.Add(async () =>
|
||||||
await updateLocalRoomSettings(joinedRoom.Settings);
|
{
|
||||||
});
|
if (Room != null)
|
||||||
|
throw new InvalidOperationException("Cannot join a multiplayer room while already in one.");
|
||||||
|
|
||||||
|
Debug.Assert(room.RoomID.Value != null);
|
||||||
|
|
||||||
|
// Join the server-side room.
|
||||||
|
var joinedRoom = await JoinRoom(room.RoomID.Value.Value);
|
||||||
|
Debug.Assert(joinedRoom != null);
|
||||||
|
|
||||||
|
// Populate users.
|
||||||
|
Debug.Assert(joinedRoom.Users != null);
|
||||||
|
await Task.WhenAll(joinedRoom.Users.Select(PopulateUser));
|
||||||
|
|
||||||
|
// Update the stored room (must be done on update thread for thread-safety).
|
||||||
|
await scheduleAsync(() =>
|
||||||
|
{
|
||||||
|
Room = joinedRoom;
|
||||||
|
apiRoom = room;
|
||||||
|
playlistItemId = room.Playlist.SingleOrDefault()?.ID ?? 0;
|
||||||
|
}, cancellationSource.Token);
|
||||||
|
|
||||||
|
// Update room settings.
|
||||||
|
await updateLocalRoomSettings(joinedRoom.Settings, cancellationSource.Token);
|
||||||
|
}, cancellationSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Joins the <see cref="MultiplayerRoom"/> with a given ID.
|
/// Joins the <see cref="MultiplayerRoom"/> with a given ID.
|
||||||
@ -151,14 +163,15 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
public Task LeaveRoom()
|
public Task LeaveRoom()
|
||||||
{
|
{
|
||||||
if (Room == null)
|
|
||||||
return Task.FromCanceled(new CancellationToken(true));
|
|
||||||
|
|
||||||
// Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background.
|
// Leaving rooms is expected to occur instantaneously whilst the operation is finalised in the background.
|
||||||
// However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed.
|
// However a few members need to be reset immediately to prevent other components from entering invalid states whilst the operation hasn't yet completed.
|
||||||
// For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time.
|
// For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time.
|
||||||
var scheduledReset = scheduleAsync(() =>
|
var scheduledReset = scheduleAsync(() =>
|
||||||
{
|
{
|
||||||
|
// The join may have not completed yet, so certain tasks that either update the room or reference the room should be cancelled.
|
||||||
|
// This includes the setting of Room itself along with the initial update of the room settings on join.
|
||||||
|
joinCancellationSource?.Cancel();
|
||||||
|
|
||||||
apiRoom = null;
|
apiRoom = null;
|
||||||
Room = null;
|
Room = null;
|
||||||
CurrentMatchPlayingUserIds.Clear();
|
CurrentMatchPlayingUserIds.Clear();
|
||||||
@ -169,7 +182,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return joinOrLeaveTaskChain.Add(async () =>
|
return joinOrLeaveTaskChain.Add(async () =>
|
||||||
{
|
{
|
||||||
await scheduledReset;
|
await scheduledReset;
|
||||||
await LeaveRoomInternal();
|
await LeaveRoomInternal().CatchUnobservedExceptions();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +468,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
/// This updates both the joined <see cref="MultiplayerRoom"/> and the respective API <see cref="Room"/>.
|
/// This updates both the joined <see cref="MultiplayerRoom"/> and the respective API <see cref="Room"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="settings">The new <see cref="MultiplayerRoomSettings"/> to update from.</param>
|
/// <param name="settings">The new <see cref="MultiplayerRoomSettings"/> to update from.</param>
|
||||||
private Task updateLocalRoomSettings(MultiplayerRoomSettings settings) => scheduleAsync(() =>
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to cancel the update.</param>
|
||||||
|
private Task updateLocalRoomSettings(MultiplayerRoomSettings settings, CancellationToken cancellationToken = default) => scheduleAsync(() =>
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
return;
|
return;
|
||||||
@ -473,10 +487,17 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
|
|
||||||
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
var req = new GetBeatmapSetRequest(settings.BeatmapID, BeatmapSetLookupType.BeatmapId);
|
||||||
req.Success += res => updatePlaylist(settings, res);
|
|
||||||
|
req.Success += res =>
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
updatePlaylist(settings, res);
|
||||||
|
};
|
||||||
|
|
||||||
api.Queue(req);
|
api.Queue(req);
|
||||||
});
|
}, cancellationToken);
|
||||||
|
|
||||||
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet)
|
private void updatePlaylist(MultiplayerRoomSettings settings, APIBeatmapSet onlineSet)
|
||||||
{
|
{
|
||||||
@ -524,12 +545,15 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
CurrentMatchPlayingUserIds.Remove(userId);
|
CurrentMatchPlayingUserIds.Remove(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task scheduleAsync(Action action)
|
private Task scheduleAsync(Action action, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
Scheduler.Add(() =>
|
Scheduler.Add(() =>
|
||||||
{
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
action();
|
action();
|
||||||
|
Loading…
Reference in New Issue
Block a user