- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我有一个名为 SendWithReplyAsync
的方法,它使用 TaskCompletionSource
在完成时发出信号并从服务器返回回复。
我正在尝试通过一种方法从服务器获得 2 个回复,该方法还需要更改场景、更改转换等,因此它需要在主线程上,据我所知。
unity中该方法绑定(bind)到一个ui按钮的OnClick()
:
public async void RequestLogin()
{
var username = usernameField.text;
var password = passwordField.text;
var message = new LoginRequest() { Username = username, Password = password };
var reply = await client.SendWithReplyAsync<LoginResponse>(message);
if (reply.Result)
{
Debug.Log($"Login success!");
await worldManager.RequestSpawn();
}
else Debug.Log($"Login failed! {reply.Error}");
}
如您所见,调用了 await WorldManager.RequestSpawn();
该方法如下所示:
public async Task RequestSpawn()
{
//Get the initial spawn zone and transform
var spawnReply = await client.SendWithReplyAsync<PlayerSpawnResponse>(new PlayerSpawnRequest());
//Load the correct zone
SceneManager.LoadScene("TestingGround");
//Move the player to the correct location
var state = spawnReply.initialState;
player.transform.position = state.Position.Value.ToVector3();
player.transform.rotation = Quaternion.Euler(0, state.Rotation.Value, 0);
//The last step is to get the visible entities at our position and create them before closing the loading screen
var statesReply = await client.SendWithReplyAsync<InitialEntityStatesReply>(new InitialEntityStatesRequest());
SpawnNewEntities(statesReply);
}
所以当我单击按钮时,我可以看到(服务器端)所有消息(登录请求、生成请求和初始实体状态请求)都在发送。然而,统一中什么也没有发生。没有场景变化,(显然)没有位置或旋转更新。
我有一种感觉,当涉及到统一时,我对 async/await 不了解,而且我的 RequestSpawn
方法没有在主线程上运行。
我尝试使用 client.SendWithReplyAsync(...).Result
并删除所有方法上的 async
关键字,但这只会导致死锁。我在 Stephen Cleary 的博客上阅读了更多关于死锁的信息 here (他的网站似乎占用了 100% 的 cpu.. 只有我一个吗?)
我真的不确定如何让它工作。
如果您需要它们,这里是发送/接收消息的方法:
public async Task<TReply> SendWithReplyAsync<TReply>(Message message) where TReply : Message
{
var task = msgService.RegisterReplyHandler(message);
Send(message);
return (TReply)await task;
}
public Task<Message> RegisterReplyHandler(Message message, int timeout = MAX_REPLY_WAIT_MS)
{
var replyToken = Guid.NewGuid();
var completionSource = new TaskCompletionSource<Message>();
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(timeout);
//TODO Make sure there is no leakage with the call to Token.Register()
tokenSource.Token.Register(() =>
{
completionSource.TrySetCanceled();
if (replyTasks.ContainsKey(replyToken))
replyTasks.Remove(replyToken);
},
false);
replyTasks.Add(replyToken, completionSource);
message.ReplyToken = replyToken;
return completionSource.Task;
}
这是完成任务的地点/方式:
private void HandleMessage<TMessage>(TMessage message, object sender = null) where TMessage : Message
{
//Check if the message is in reply to a previously sent one.
//If it is, we can complete the reply task with the result
if (message.ReplyToken.HasValue &&
replyTasks.TryGetValue(message.ReplyToken.Value, out TaskCompletionSource<Message> tcs) &&
!tcs.Task.IsCanceled)
{
tcs.SetResult(message);
return;
}
//The message is not a reply, so we can invoke the associated handlers as usual
var messageType = message.GetType();
if (messageHandlers.TryGetValue(messageType, out List<Delegate> handlers))
{
foreach (var handler in handlers)
{
//If we have don't have a specific message type, we have to invoke the handler dynamically
//If we do have a specific type, we can invoke the handler much faster with .Invoke()
if (typeof(TMessage) == typeof(Message))
handler.DynamicInvoke(sender, message);
else
((Action<object, TMessage>)handler).Invoke(sender, message);
}
}
else
{
Debug.LogError(string.Format("No handler found for message of type {0}", messageType.FullName));
throw new NoHandlersException();
}
}
万幸,传奇的 Stephen Cleary 看到了这个
最佳答案
假设您通过最新版本的 Unity 使用异步/等待,并将“.NET 4.x 等效”设置为脚本运行时版本,那么您编写的 RequestSpawn()
方法应该是在 Unity 的主线程上运行。您可以通过调用验证:
Debug.Log(System.Threading.Thread.CurrentThread.ManagedThreadId);
以下简单测试使用 Unity 2018.2(下面的输出)为我正确加载新场景:
public async void HandleAsync()
{
Debug.Log($"Foreground: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
await WorkerAsync();
Debug.Log($"Foreground: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
}
private async Task WorkerAsync()
{
await Task.Delay(500);
Debug.Log($"Worker: {Thread.CurrentThread.ManagedThreadId}");
await Task.Run((System.Action)BackgroundWork);
await Task.Delay(500);
SceneManager.LoadScene("Scene2");
}
private void BackgroundWork()
{
Debug.Log($"Background: {Thread.CurrentThread.ManagedThreadId}");
}
输出:
Foreground: 1
Worker: 1
Background: 48
Foreground: 1
关于c# - 如何从必须留在主线程上的方法统一调用异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51472393/
我是一名优秀的程序员,十分优秀!