从 Java 到 Kotlin:同步和锁定/等待/通知模式



原始上下文

早在开发Android/Java应用程序时,我创建了一个简单的类,该类允许我同步taks,而不是使用队列逻辑和/或回调模式。

事实上,这将允许我以线性方式执行一系列任务(在查看代码时(。在某些情况下,这更有用,更易于维护和调试。

这种需求背后的原因是,当我需要使用提供异步逻辑和回调以通知结果或失败的 API 时,例如 Android 蓝牙 LE API 或 Glide(仅举几例(,我希望能够在使用异步 API 时同步我的代码

这就是我想出的:

锁.java

import android.support.annotation.Nullable;
import android.util.Log;

/**
* This class is a small helper to provide feedback on lock wait and notify process
*/
public final class Lock<T> {
private final static String TAG = Lock.class.getSimpleName();
private T result = null;
private boolean isWaiting = false;
private boolean wasNotified = false;

/**
* Releases the lock on this instance to notify the thread that was waiting and to provide the result
* @param result    the result of the asynchronous operation that was waited
*/
public synchronized final void setResultAndNotify(@Nullable final T result) {
this.result = result;
if (this.isWaiting) {
synchronized (this) {
this.notify();
}
} else
this.wasNotified = true;
}

/**
* This method locks on the current instance and wait for the duration specified in milliseconds
* @param timeout the duration, in milliseconds, the thread will wait if it does not get notify
*/
@Nullable
public synchronized final T waitAndGetResult(final long timeout) {
if (this.wasNotified) { // it might happen that the notify was performed even before the wait started!
this.wasNotified = false;
return this.result;
}
try {
synchronized (this) {
this.isWaiting = true;
if (timeout < 0) {
this.wait();
} else {
this.wait(timeout);
}
this.isWaiting = false;
return this.result;
}
} catch (final InterruptedException e) {
Log.e(TAG, "Failed to wait, Thread got interrupted -> " + e.getMessage());
Log.getStackTraceString(e);
}
return null;
}

/**
* This method locks on the current instance and wait as long necessary (no timeout)
*/
@Nullable
public synchronized final T waitAndGetResult() {
return waitAndGetResult(-1)
}

/**
* Tells whether this instance is currently waiting or not
* @return <code>true</code> if waiting, <code>false</code> otherwise
*/
public final boolean isWaiting() {
return this.isWaiting;
}
}

我敢肯定,这不是最好的解决方案,但是,在某种程度上,它确实在我极少数情况下处理了一些竞争条件,在这些情况下,notify可以在wait之前调用。我相信我可以使用AtomicBoolean但到目前为止没有问题。

以下是它的使用方法:

public final boolean performLongRunningTasks() {
this.asynchronousTaskA.startAsync();
final Result taskAResult = this.taskALock.waitAndGetResult(); // wait forever
this.asynchronousTaskB.startAsync();
final Result taskBResult = this.taskBLock.waitAndGetResult(5000L); // wait up to 5 seconds
return taskAResult != null && taskBResult != null;
}

// callback
private void onTaskACompleted(@Nullable Result result) {
this.taskALock.setResultAndNotify(result);
}

// callback
private void onTaskBCompleted(@Nullable Result result) {
this.taskBLock.setResultAndNotify(result);
}

有用,对吧?诚然,永远的等待并不理想。

迁移到 Kotlin

现在我正在 Kotlin 中开发,我已经开始阅读有关并发和协程的信息。 有一篇文章,在某个时候,将 Java 的同步/锁定/等待/通知模式转换为 Kotlin,这有助于我将我的原始类翻译成:

Lock.kt

import android.util.Log
class Lock<T> {
private var result : T? = null
private var isWaiting = false
private var wasNotified = false
private val lock = Object() // Java object, Urgh...
fun setResultAndNotify(value: T?) {
result = value
if (isWaiting) {
synchronized(lock) {
lock.notify()
}
} else {
wasNotified = true
}
}

fun waitAndGetResult(timeout: Long) : T? {
if (wasNotified) {
wasNotified = false
return result
}
try {
synchronized(lock) {
isWaiting = true
if (timeout < 0) lock.wait() else lock.wait(timeout)
isWaiting = false
}
} catch (e: InterruptedException) {
Log.getStackTraceString(e)
}
return this.result
}

fun waitAndGetResult() = waitAndGetResult(-1)
}

这里没有问题,它确实有效...但是,这是科特林!我当然可以做得更好吗? 您的解决方案是什么?

Android 中的 Kotlin编译为相同的 Java 等效项,因此您可以在 Java 中使用您的遗留代码与 Kotlin 没有问题。这样你就不必将所有类重写为 kotlin。"按原样"使用它们。

另一个问题是什么是更好的方法?关于这一点,我会说它是 kotlin 协程!它们将作为您的代码工作,但没有任何锁和其他样板代码。用法非常简单。

1( 添加依赖:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

2( 通过向函数添加suspend修饰符来创建挂起函数:

suspend fun someBigTask(param: Param): Result {
//...
}

3( 创建作用域和帮助程序方法。将此视为将填充结果的线程。我还想为后台任务创建另一个范围:

val ioScope by lazy { CoroutineScope(Dispatchers.IO) } // background scope
// For background processing
@Suppress("SuspendFunctionOnCoroutineScope")
private suspend inline fun <T> CoroutineScope.await(crossinline block: suspend Job.() -> T): T {
val deferred = CompletableDeferred<T>()
launch {
try {
val result = deferred.block()
deferred.complete(result)
} catch (throwable: Throwable) {
deferred.completeExceptionally(throwable)
}
}
return deferred.await()
}
suspend inline fun <T> doAsync(crossinline block: suspend () -> T): T {
return ioScope.await { block() }
}

4( 现在您已准备好启动长时间运行的任务:

val job = SupervisorJob() // the job containing your performing tasks. You can use it for canceling your tasks when it is needed.
val mainScope = CoroutineScope(Dispatchers.Main) // scope of the main thread
fun launchMyJob(block: suspend () -> Unit) {
mainScope.launch(job) {
try {
block()
} catch(e: JobCancellationException) {
// is thrown when you call job.cancel() method.
// ignore it
}
}
}
fun performLongRunningTasks(param1: Param1, param2: Param2) {
launchMyJob {
val result1 = doAsync { someBigTask(param1) /* This operation is running in background thread */ }
// result1 will be written only when someBigTask() is finished and coroutine will continue
val result2 = doAsync { someBigTask(result1, param2) }
// Here you can access views because you have result on the main thread.
textView.text = result2.toString() 
}
}

5( 取消您的工作:

job.cancel()

使用此方法时要小心,因为在调用它之后,作业将无法再次使用,并且您的协程将抛出您应该处理的JobCancellationException

即使它看起来像现实中的同步代码,一切都将在不同的线程上执行。这就是 Kotlin 协程的美妙之处。

最新更新