回调转换为在runBlocking时无限地暂停函数循环

时间:2019-08-27 12:58:57

标签: android kotlin kotlin-coroutines

我正在将java类重写为kotlin,将其回调函数用suspend函数替换。这是我的Java代码:

@IgnoreExtraProperties
public class DeviceType {

public String manufacturer;
public String marketName;
public String model;

public DeviceType(String manufacturer, String marketName, String model) {
    this.manufacturer = manufacturer;
    this.marketName = marketName;
    this.model = model;
}

public DeviceType(){}

public DeviceType(Context context) {
    DeviceName.with(context).request(new DeviceName.Callback() {
        @Override
        public void onFinished(DeviceName.DeviceInfo info, Exception error) {
            if (error == null) {
                manufacturer = info.manufacturer;
                marketName = info.marketName;
                model = info.model;
            } else
                Log.e("DeviceType: ", error.getMessage());
        }

    });
}

@Override
public String toString() {
    if (model == null) {
        return "No device type recognized!";
    } else {
        if (marketName.equals(model))
            return manufacturer + " " +marketName;
        else
            return manufacturer + " " +marketName+ " (" +model+ ")";
    }
}

DeviceName类属于库AndroidDeviceNames

下面是我在Kotlin中的新代码:

@IgnoreExtraProperties
data class DeviceType(
    var manufacturer: String? = null,
    var marketName: String? = null,
    var model: String? = null
) {

    constructor(context: Context) : this(
        context.deviceType()?.manufacturer,
        context.deviceType()?.marketName,
        context.deviceType()?.model
    )

    override fun toString(): String {
        val stringSuffix =
            if (marketName == model)
                ""
            else
                " ($model)"

        return model?.let { "$manufacturer $marketName$stringSuffix" }
            ?: "No device type recognized!"
    }
}

/**
 * return DeviceType "from" UI Context
 */
fun Context.deviceType(): DeviceType? = runBlocking {
    withContext(Dispatchers.IO) {
        /*
        delay(1000L)
        DeviceType("Nokia","Banana","R2D2")
        ^
        This works!
        */
        DeviceName
            .with(this@deviceType)
            .awaitWith(this@deviceType)
        //  ^ that doesn't!
    }
}

suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? = suspendCoroutine { cont ->
    DeviceName.with(context).request { info, error ->
        if (error == null) {
            cont.resume(DeviceType(
                info.manufacturer, 
                info.marketName, 
                info.model
            ))
        } else
            cont.resumeWithException(Throwable(error.message))
                .let {
                    Log.e(
                        "FirebaseUserData",
                        "DeviceName.Request.awaitWith(): $error.message"
                    )
                }
    }
}

deviceType().toString())中执行MainActivity将在runBlocking()函数中进行无限循环。 基本的问题当然是“为什么我的awaitWith()的实现不起作用?”,但我也很感兴趣,如果我应该为异常处理提供其他解决方案,请在kotlin和协程中迈出第一步,因为我阅读了“协程可能会隐藏异常”。

还有一个问题:

Dispatcher.IO可以吗? DeviceName从Google API json查询获取数据。 我是否也应该将该调度程序类型用于与Firebase DB相关的协程?

2 个答案:

答案 0 :(得分:0)

首先,响应问题的标题,循环正在发生,因为构造函数正在调用Context.deviceType(),而DeviceName.Request.awaitWith再次调用了构造函数:

cont.resume(DeviceType(
                info.manufacturer, 
                info.marketName, 
                info.model
            ))

Context.deviceType()本身返回一个DeviceType,但是您希望使用它来配置初始化中的每个属性。每个DeviceType的属性初始化都会实例化一个DeviceType,每个属性会实例化另一个DeviceType,依此类推。...


使用Dispatcher.IO是可以的,甚至在进行IO操作(如网络)时也可以使用runBlocking,但您并没有完全使用它。

## Assume we are on Thread (A) fun Context.deviceType(): DeviceType? = runBlocking { ## Still in thread (A) withContext(Dispatchers.IO) { ## Execute in an IO thread pool, but (A) is waiting DeviceName .with(this@deviceType) .awaitWith(this@deviceType) } ## Returns to thread (A) } # Resumes Thread (A) 调用将阻止当前线程。您使用的方式是这样的:

                   AlertDialog.Builder mBuilder = new AlertDialog.Builder(getActivity());
                   View mView=getActivity().getLayoutInflater().inflate(R.layout.custom_layout, null);
                    mBuilder.setView(mView);
                    Button close = (Button) mView.findViewById(R.id.close);
                    Button OK = (Button) mView.findViewById(R.id.ok);

                    close.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                         //close the dialog
                            hideDialog1();
                        }
                    });

                    OK.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                         //do something
                        }
                    });

                    dialog = mBuilder.create();
                    dialog.setCanceledOnTouchOutside(false);
                    dialog.show();

因此,尽管这有点在IO调度程序中运行,但调用线程将被阻塞,直到执行完成为止,从而使其同步且无关紧要。

答案 1 :(得分:0)

实际上,我的目标是在非协程环境中查看deviceType()函数的输出。无论如何,此函数都将在其他暂停函数或协程范围中使用。

这是DeviceType类,其公共函数没有其他构造函数:

    @IgnoreExtraProperties
    data class DeviceType(
        var manufacturer: String? = null,
        var marketName: String? = null,
        var model: String? = null
    ) {
        override fun toString(): String {
            val stringSuffix =
                if (marketName == model)
                    ""
                else
                    " ($model)"

            return model?.let { "$manufacturer $marketName$stringSuffix" }
                ?: "No device type recognized!"
        }
    }

    fun Context.deviceTypeByRunBlocking(): DeviceType? = runBlocking {
        withContext(Dispatchers.IO) {
            DeviceName
                .with(this@deviceTypeNoSuspend)
                .awaitWith(this@deviceTypeNoSuspend)
        }
    }

    suspend fun Context.deviceType(): DeviceType? =
        DeviceName
            .with(this@deviceType)
            .awaitWith(this@deviceType)

    private suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? =
        suspendCoroutine { cont ->
            DeviceName.with(context).request { info, error ->
                if (error == null) {
                    cont.resume(
                        DeviceType(
                            info.manufacturer,
                            info.marketName,
                            info.model
                        )
                        //.also{Log.d("TAG","Inside awaitWith(): $it")}
                    )
                } else
                    cont.resumeWithException(Throwable(error.message))
                        .let {
                            Log.e(
                                "TAG",
                                "DeviceName.Request.awaitWith(): $error.message"
                            )
                        }
            }
        }

主要活动:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    GlobalScope.launch { Log.d("MainActivity", "${this@MainActivity.deviceType()}") }
    //^ this works

    Log.d("MainActivity", "${this.deviceTypeByRunBlocking()}")
    //^ this still does not, loops in joinBlocking(), isComplete = false
    }
}

我知道不建议使用GlobalScope,但是对于我来说,进行测试很合适。