为什么协程异常处理程序将原始异常加倍?
why the coroutine exception handler double the original exception?
我实现了自己的 async
,我无法以正确的方式处理异常。为什么?
val expected = IllegalStateException();
val it = async<Any> {
throw expected;
};
assert.that({ it.get() }, throws(equalTo(expected)));
// ^--- but it throws a IllegalStateException(cause = expected)
源代码
interface Response<in T> {
suspend fun yield(value: T);
}
interface Request<out T> {
fun get(): T;
fun <R> then(mapping: (T) -> R): Request<R>;
}
private val executor: ExecutorService = ForkJoinPool(20);
fun <T> async(block: suspend Response<T>.() -> Unit): Request<T> {
return object : Request<T>, Response<T> {
@Volatile var value: T? = null;
var request: Continuation<Unit>? = block.createCoroutine(this, delegate {}).let {
var task: Future<*>? = executor.submit { it.resume(Unit); };
return@let delegate {
try {
val current = task!!;
task = null;
current.get();
} catch(e: ExecutionException) {
throw e.cause ?: e;
}
};
};
override fun <R> then(mapping: (T) -> R): Request<R> = async<R> {
yield(mapping(get()));
};
override fun get(): T {
return value ?: wait();
}
private fun wait(): T {
val it = request!!;
request = null;
it.resume(Unit);
return value!!;
}
suspend override fun yield(value: T) {
this.value = value;
}
};
}
inline fun <T> delegate(noinline exceptional: (Throwable) -> Unit = { throw it; }, crossinline resume: (T) -> Unit): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = EmptyCoroutineContext;
override fun resumeWithException(exception: Throwable) {
exceptional(exception);
}
override fun resume(value: T) {
resume(value);
}
}
}
奇怪的行为来自 java。 ForkJoinTask#getThrowableException
将为给定任务重新抛出异常:
Returns a rethrowable exception for the given task, if
available. To provide accurate stack traces, if the exception
was not thrown by the current thread, we try to create a new
exception of the same type as the one thrown, but with the
recorded exception as its cause. If there is no such
constructor, we instead try to use a no-arg constructor,
followed by initCause
, to the same effect. If none of these
apply, or any fail due to other exceptions, we return the
recorded exception, which is still correct, although it may
contain a misleading stack trace.
这意味着如果您不想为给定任务重新抛出异常,您可以将异常构造函数设为非公开,例如:
val exception = object: IllegalStateException(){/**/};
// ^--- its constructor only available in its scope
我实现了自己的 async
,我无法以正确的方式处理异常。为什么?
val expected = IllegalStateException();
val it = async<Any> {
throw expected;
};
assert.that({ it.get() }, throws(equalTo(expected)));
// ^--- but it throws a IllegalStateException(cause = expected)
源代码
interface Response<in T> {
suspend fun yield(value: T);
}
interface Request<out T> {
fun get(): T;
fun <R> then(mapping: (T) -> R): Request<R>;
}
private val executor: ExecutorService = ForkJoinPool(20);
fun <T> async(block: suspend Response<T>.() -> Unit): Request<T> {
return object : Request<T>, Response<T> {
@Volatile var value: T? = null;
var request: Continuation<Unit>? = block.createCoroutine(this, delegate {}).let {
var task: Future<*>? = executor.submit { it.resume(Unit); };
return@let delegate {
try {
val current = task!!;
task = null;
current.get();
} catch(e: ExecutionException) {
throw e.cause ?: e;
}
};
};
override fun <R> then(mapping: (T) -> R): Request<R> = async<R> {
yield(mapping(get()));
};
override fun get(): T {
return value ?: wait();
}
private fun wait(): T {
val it = request!!;
request = null;
it.resume(Unit);
return value!!;
}
suspend override fun yield(value: T) {
this.value = value;
}
};
}
inline fun <T> delegate(noinline exceptional: (Throwable) -> Unit = { throw it; }, crossinline resume: (T) -> Unit): Continuation<T> {
return object : Continuation<T> {
override val context: CoroutineContext = EmptyCoroutineContext;
override fun resumeWithException(exception: Throwable) {
exceptional(exception);
}
override fun resume(value: T) {
resume(value);
}
}
}
奇怪的行为来自 java。 ForkJoinTask#getThrowableException
将为给定任务重新抛出异常:
Returns a rethrowable exception for the given task, if available. To provide accurate stack traces, if the exception was not thrown by the current thread, we try to create a new exception of the same type as the one thrown, but with the recorded exception as its cause. If there is no such constructor, we instead try to use a no-arg constructor, followed by
initCause
, to the same effect. If none of these apply, or any fail due to other exceptions, we return the recorded exception, which is still correct, although it may contain a misleading stack trace.
这意味着如果您不想为给定任务重新抛出异常,您可以将异常构造函数设为非公开,例如:
val exception = object: IllegalStateException(){/**/};
// ^--- its constructor only available in its scope