3.6 Coroutine变换
Coroutine 是Unity标准的异步处理,UniRx可以把Coroutine 转换成Observable
3.6.1 Coroutine => Observable
等待 Coroutine 执行完
Observable.FromCoroutine 把Coroutine 变成Observable,生成的Observable在对象Coroutine结束时发送消息
Observable.FromCoroutine把Coroutine 和Observable 绑定在一起,当Subscribe() 被Dispose()时,执行中的Coroutine 会自动停止
// 不使用 CancellationToken 的时候
public static IObservable<Unit> FromCoroutine(
Func<IEnumerator> coroutine,
bool publishEveryYield = false)
// 使用 CancellationToken 的时候
public static IObservable<Unit> FromCoroutine(
Func<CancellationToken, IEnumerator> coroutine,
bool publishEveryYield = false)
使用例 等待Coroutine 执行完了
using System;
using System.Collections;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class FromCoroutineSample1 : MonoBehaviour
{
private void Start()
{
// Coroutine 结束时用 Observable 接受消息
Observable.FromCoroutine(WaitingCoroutine, publishEveryYield: false)
.Subscribe(
_ => Debug.Log("OnNext"),
() => Debug.Log("OnCompleted"))
.AddTo(this);
// ToObservable() 简单写法 糖语法
// WaitingCoroutine().ToObservable()
// .Subscribe();
}
private IEnumerator WaitingCoroutine()
{
Debug.Log("Coroutine start.");
// 等待3s
yield return new WaitForSeconds(3);
Debug.Log("Coroutine finish.");
}
}
}
使用例 使用 CancellationToken
CancellationToken 可以把由FromCoroutine 生成的Observable 订阅中断取消,同时也停止这个协程
using System;
using System.Collections;
using System.Threading;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class FromCoroutineSample2 : MonoBehaviour
{
private void Start()
{
// 使用 CancellationToken
Observable
.FromCoroutine(token => WaitingCoroutine(token))
.Subscribe(
_ => Debug.Log("OnNext"),
() => Debug.Log("OnCompleted"))
.AddTo(this);
}
// CancellationToken 作为参数
private IEnumerator WaitingCoroutine(CancellationToken token)
{
Debug.Log("Coroutine start.");
// Observable 在等待Coroutine 时
// 当这个Coroutine 停止时
// yield return 让等待着的Observable 也停下来
// 所以 使用 CancellationToken
yield return Observable
.Timer(TimeSpan.FromSeconds(3))
.ToYieldInstruction(token); // 因为Observable 停下来了 到这会停止Coroutine
Debug.Log("Coroutine finish.");
}
}
}
** Coroutine 生成任意的Observable **
Observable.FromCoroutine 可以生成 发送任意消息的Observable
通过委托 将消息传递出去
public static IObservable<T> FromCoroutine<T>(
Func<IObserver<T>, IEnumerator> coroutine)
public static IObservable<T> FromCoroutine<T>(
Func<IObserver<T>, CancellationToken, IEnumerator> coroutine)
**使用例 **
using System;
using System.Collections;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class FromCoroutineSample3 : MonoBehaviour
{
//长按判定时间
private readonly float _longPressThresholdSeconds = 1.0f;
private void Start()
{
// 检测一定时间长按的Observable
Observable.FromCoroutine<bool>(observer => LongPushCoroutine(observer))
.DistinctUntilChanged() // 去除重复的消息
.Subscribe(x => Debug.Log(x)).AddTo(this);
}
// 检测空格键被长按
// 经过一定时间返回true
// 放开按键 返回false
private IEnumerator LongPushCoroutine(IObserver<bool> observer)
{
var isPushed = false;
var lastPushTime = Time.time;
while (true)
{
if (Input.GetKey(KeyCode.Space))
{
if (!isPushed)
{
// 按下的时间
lastPushTime = Time.time;
isPushed = true;
}
else if (Time.time - lastPushTime > _longPressThresholdSeconds)
{
// 按下时发送true
observer.OnNext(true);
}
}
else
{
if (isPushed)
{
// 抬起时发送false
observer.OnNext(false);
isPushed = false;
}
}
yield return null;
}
}
}
}
** 只是用 yield return null 的Coroutine 的最优化**
Observable.FromCoroutine 的派生、性能更好的Observable.FromMicroCoroutine,这里内部只使用了yield return null
比 Observable.FromCoroutine 相对轻量
使用方法和 Observable.FromCoroutine一样 ,根据状况不同,区别使用
// 等待协程结束
public static IObservable<Unit> FromMicroCoroutine(
Func<IEnumerator> coroutine,
bool publishEveryYield = false,
FrameCountType frameCountType = FrameCountType.Update)
// 等待协程结束 使用 CancellationToken
public static IObservable<Unit> FromMicroCoroutine(
Func<CancellationToken, IEnumerator> coroutine,
bool publishEveryYield = false,
FrameCountType frameCountType = FrameCountType.Update)
// 协程生成 任意的 Observable
public static IObservable<T> FromMicroCoroutine<T>(
Func<IObserver<T>, IEnumerator> coroutine,
FrameCountType frameCountType = FrameCountType.Update)
// 协程生成 任意的 Observable 使用 CancellationToken
public static IObservable<T> FromMicroCoroutine<T>(
Func<IObserver<T>, CancellationToken, IEnumerator> coroutine,
FrameCountType frameCountType = FrameCountType.Update)
**使用例 **
using System.Collections;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class FromMicroCoroutineSample : MonoBehaviour
{
private void Start()
{
// FromCoroutine
Observable
.FromCoroutine(() => WaitingCoroutine(5))
.Subscribe();
// 对象协程只使用了 yield return null
// 可以使用 FromMicroCoroutine
Observable
.FromMicroCoroutine(() => WaitingCoroutine(5))
.Subscribe();
}
// 等待指定秒数
private IEnumerator WaitingCoroutine(float seconds)
{
var start = Time.time;
while (Time.time - start <= seconds)
{
yield return null;
}
}
}
}
用 yield return 发送消息
Observable.FromCoroutineValue 可以把 yield return 的值 作为OnNext 消息取出
// 不使用 CancellationToken
public static IObservable<T> FromCoroutineValue<T>(
Func<IEnumerator> coroutine,
bool nullAsNextUpdate = true)
// 使用 CancellationToken
public static IObservable<T> FromCoroutineValue<T>(
Func<CancellationToken, IEnumerator> coroutine,
bool nullAsNextUpdate = true)
3.6.2 Observable => Coroutine
反过来 Observable 通过 YieldInstruction 让一个Coroutine 来接受
ToYieldInstruction
通过 ToYieldInstruction ,让Observable 变换成CustomYieldInstruction
可以实现 await/ async 相似的举动
使用例
using System;
using System.Collections;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class YieldInstructionSample1 : MonoBehaviour
{
private void Start()
{
StartCoroutine(WaitCoroutine());
}
private IEnumerator WaitCoroutine()
{
Debug.Log("Coroutine start:" + Time.time);
// Observable => Coroutine
yield return Observable
.Timer(TimeSpan.FromSeconds(1))
.ToYieldInstruction();
Debug.Log("Coroutine end:" + Time.time);
}
}
}
等待一个无限长的 Observable
通过ToYieldInstruction() 变换的Observable 在发送OnCompleted消息之前都是处于未完成状态,所以当Observable 会无限长的情况下需要使用Take(1) 之类的 Operator来发送OnCompleted消息
使用例
using System.Collections;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
namespace Sample.Section3.Coroutines
{
public class YieldInstructionSample2 : MonoBehaviour
{
[SerializeField] private Button _moveButton;
private void Start()
{
StartCoroutine(MoveCoroutine());
}
// 按下 Button 在1秒间 Object 会前进
private IEnumerator MoveCoroutine()
{
while (true)
{
// 等待 Button 按下
// OnClickAsObservable()是无限长的Stream、使用Take(1) 执行1次
yield return _moveButton
.OnClickAsObservable()
.Take(1)
.ToYieldInstruction();
var start = Time.time;
while (Time.time - start <= 1.0f)
{
//前进
transform.position += Vector3.forward * Time.deltaTime;
yield return null;
}
}
}
}
}
取出 Observable 的结果
ToYieldInstruction() 可以作为变数,Observable 的值 在其的Result 属性里
使用例
using System;
using System.Collections;
using System.IO;
using UniRx;
using UnityEngine;
namespace Sample.Section3.Coroutines
{
public class YieldInstructionSample3 : MonoBehaviour
{
private void Start()
{
StartCoroutine(ReadFileCoroutine());
}
// 异步读取FILE
private IEnumerator ReadFileCoroutine()
{
// Observable 进行 YieldInstruction 变换
// throwOnError 为 false 时
// 它将保存失败时的异常。
// (如果为真,就会按原样抛出异常)
var yi = ReadFileAsync(@"data.txt")
.ToYieldInstruction(throwOnError: false);
// 等待
yield return yi;
if (yi.HasError) // HasError 是否出错
{
// 失败时 保存异常信息
Debug.LogError(yi.Error);
}
else
{
// 成功是输出 Result
Debug.Log(yi.Result);
}
}
// 生成 异步读取FIle 的IObservable
private IObservable<string> ReadFileAsync(string path)
{
return Observable.Start(() =>
{
using (var r = new StreamReader(path))
{
return r.ReadToEnd();
}
}, Scheduler.ThreadPool);
}
}
}