Dart 및 Rust: 비동기식 스토리🔃

33031 단어 ffidartrustflutter
이전 논문에서: 우리는 두 언어를 어떻게 연결하고 더욱 안전하고 성능이 좋은flatter 응용 프로그램을 구축하는지 보여 주었다.
이 블로그 글에서 우리는 어떻게 다중 스레드 비동기 Rust 코드와 비동기 Dart를 결합하여 사용하는지, 그리고 여러 개의 실행할 때의 응용 프로그램을 구축하는지, 이런 실행할 때 sōzu처럼 원활하게 협동하여 작업할 수 있는지 토론할 것이다.

문제🎯


여러 개의 비동기 순환을 실행하는 것은 항상 정확하지 않습니다. Dart VM이 자신만의 비동기 운행을 가지고 있다는 것을 알고 있을 때,Rust에서 우리가 어떤 유행하는 운행을 삽입할 수 있을 때, 여기에는 Tokio, async-std을 참고할 수 있습니다. 최근 smol은 비동기 std에 내장되어 있습니다. 이것은 저급 운행이기 때문입니다.
문제는 Dart가 하나의 언어로서 단선으로 설계되었다는 것이다. 그 자체는 문제가 아니지만, 우리는 잠시 후에 Rust를 사용할 때 이것이 어떤 문제가 될지 보게 될 것이다.
좋습니다. Isolate여 개를 실행할 수 있습니다. 이것은 마치 소형 Dart 가상 기기의 실례와 같습니다. 그들은 함께 일하고 메시지 전달을 통해 통신할 수 있습니다. 다음은 Flatter 팀의 Andrew가 Dart 격리와 사건 순환에 대해 이야기하는 곳입니다. 만약에 Dart 격리를 모른다면 계속하기 전에 보는 것을 강력히 권장합니다(PS: 6분 미만).😅)

PC방 긁어달래요.🌍


이 글을 더욱 감동시키기 위해, Rust에 간단한 웹 캡처 라이브러리를 구축하고, 이를 Flatter 응용 프로그램에 사용합니다.
지난 블로그 글에서 두 언어를 어떻게 연결하는지에 대한 경험에 따라 우리는 여기서도 같은 생각을 사용할 것이다.
먼저 녹슨 판자 상자를 만듭니다.
$ cargo new --lib native/scrap # yes, it is a scrap lol
쿨, 이제 비동기 웹 요청을 보내는 방법이 필요해요. 그래서 reqwest을 사용하도록 하겠습니다.
[dependencies]
# we will use `rustls-tls` here since openssl is an issue when cross-compiling for Android/iOS
reqwest = { version = "0.10", default-features = false, features = ["rustls-tls"] }
이제 녹슨 코드를 작성합시다😀
// lib.rs
use std::{error, fmt, io};

/// A useless Error just for the Demo
#[derive(Copy, Clone, Debug)]
pub struct ScrapError;

impl fmt::Display for ScrapError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error While Scrapping this page.")
    }
}

impl error::Error for ScrapError {}

impl From<reqwest::Error> for ScrapError {
    fn from(_: reqwest::Error) -> Self {
        Self
    }
}

impl From<io::Error> for ScrapError {
    fn from(_: io::Error) -> Self {
        Self
    }
}

/// Load a page and return its HTML body as a `String`
pub async fn load_page(url: &str) -> Result<String, ScrapError> {
    Ok(reqwest::get(url).await?.text().await?)
}

보시다시피 이것은 간단한 코드로 대량의 오류 처리를 가지고 있습니다😂
다음으로, 우리는 우리의 FFI를 scrap 판자 상자에 귀속합니다.
$ cargo new --lib native/scrap-ffi
저희 Cargo.toml에서.
[lib]
name = "scrap_ffi"
crate-type = ["cdylib", "staticlib"]

[dependencies]
scrap = { path = "../scrap" }
지금 그것은 더러워졌다. 왜냐하면 우리는 몇 가지 일을 고려해야 하기 때문이다

실행 시 설정🔌


우리는 Tokio을 우리의 운행으로 선택할 것이다. 어떤 운행을 사용하는지는 중요하지 않지만, 우리의 예에서 reqwest은 Tokio와 잘 어울리기 때문에 우리는 그것을 사용할 것이다.
이 밖에 lazy_static은 1초 안에 편리할 것이다.
tokio = { version = "0.2", features = ["rt-threaded"] }
lazy_static = "1.4"
실행 시 설정할 코드를 작성합니다
// lib.rs
use tokio::runtime::{Builder, Runtime};
use lazy_static::lazy_static;
use std::io;

lazy_static! {
    static ref RUNTIME: io::Result<Runtime> = Builder::new()
        .threaded_scheduler()
        .enable_all()
        .core_threads(4)
        .thread_name("flutterust")
        .build();
}

/// Simple Macro to help getting the value of the runtime.
macro_rules! runtime {
    () => {
        match RUNTIME.as_ref() {
            Ok(rt) => rt,
            Err(_) => {
                return 0;
            }
        }
    };
}
이것은 4개의 라인을 포함하는 Tokio을 실행할 때 설정하고 각 라인을 flutterust으로 명명합니다.

오류 처리🧰


외국 금융 기구 간의 잘못을 처리하는 것은 어려운 것이기 때문에 우리는 조수 상자를 사용할 것이다. 이것은 우리의 생활을 더욱 쉽게 할 것이다.
의존 항목에 ffi_helpers 추가
ffi_helpers = "0.2"
이것은 우리가 오류를 처리하는 데 도움을 줄 수 있는 유용한 함수와 매크로를 제공합니다. last_error_lengtherror_message_utf8을 공개하기 때문에 Dart 방면에서 우리는 읽을 수 있는 오류 메시지를 얻을 수 있습니다.
macro_rules! error {
    ($result:expr) => {
        error!($result, 0);
    };
    ($result:expr, $error:expr) => {
        match $result {
            Ok(value) => value,
            Err(e) => {
                ffi_helpers::update_last_error(e);
                return $error;
            }
        }
    };
}

macro_rules! cstr {
    ($ptr:expr) => {
        cstr!($ptr, 0);
    };
    ($ptr:expr, $error:expr) => {{
        null_pointer_check!($ptr);
        error!(unsafe { CStr::from_ptr($ptr).to_str() }, $error)
    }};
}


#[no_mangle]
pub unsafe extern "C" fn last_error_length() -> i32 {
    ffi_helpers::error_handling::last_error_length()
}

#[no_mangle]
pub unsafe extern "C" fn error_message_utf8(buf: *mut raw::c_char, length: i32) -> i32 {
    ffi_helpers::error_handling::error_message_utf8(buf, length)
}

load\u 페이지 기능 공개🔑


이제 문제가 생겼습니다. async 함수를 어떻게 공개할까요?응, 다트는 러스트의 async이 어떻게 일하는지 몰라.

한 가지 방법은 일반적인 비async 함수를 async 함수로 하는 포장기를 작성한 다음에 block_on을 호출하여 async fn에서 결과를 얻을 수 있습니다. 보시다시피 우리는 이 라인이 async 임무만 실행하는 것을 막을 것입니다. 그러면 우선 어떤 장점이 있습니까?
또 다른 방법이 있습니다. callbacks 을 사용하는 것이 어떻습니까?!
좋습니다. 이것은 옵션입니다. 자바스크립트 Promise 스타일을 사용하여 두 개의 리셋을 전달하여 비동기적인 작업을 처리합니다. 하나는 success, 다른 하나는 err을 호출합니다.
이론적으로는 가능하지만,

내가 전에 말한 바와 같이, Dart는 단일 스레드로 설계되었기 때문에, 너는 다른 스레드에서만 리셋을 호출할 수 없다. 그것은 기본적으로 Dart의 의미를 파괴할 것이다. (너는 더 많은 here을 읽을 수 있다.)
그럼 해결 방안은 무엇일까요?
만약 내가 너에게 말한다면, 우리는 Isolate을 사용하여 다트와 러스트 사이에서 통신할 수 있을 것이다😦.
새 격리 장치마다 SendPortReceivePort이 있습니다. Isolate과 통신하는 데 사용됩니다. SendPortIsolate으로 메시지를 보내기만 하면 됩니다. SendPort마다 NativePort이 있습니다. 이것은 이 포트의 기본 번호입니다.

Just a small note, the term of Port here is not related at all to a Networking Port or something like that, it is just numbers used as Keys for a Map implemented internally in Dart VM, think of them as a Handle to that Isolate


만약 우리가 이 숫자를 사용하여 우리가 원하는 모든 라인에서 async fn 결과를 보낸다면, 이것은 가능합니까?맞다

입력: Allo Isolate📞


allo-isolate은 여러 스레드 Rust 및 Dart VM(독립형)을 실행할 수 있도록 Sunshine에 구축된 케이스입니다.🌀Port의 격리만 있으면 녹슨 코드로부터 어떤 메시지를 보낼 수 있습니다.
엔진 덮개 아래에서 Dart API Dart_PostCObject 함수를 사용하여 대부분을 변환할 수 있습니다(거의 all?)녹식 유형부터 성도 유형까지 자세한 내용은 docs을 참조하십시오.

Allo (pronounced Hello, without the H) usually used in communication over the phone in some languages like Arabic :).


예를 들어 load_page 함수를 공개하는 방법을 알고 있습니다. allo-isolate을 사용하겠습니다.
allo-isolate = "0.1"
그것을 사용하다
use allo_isolate::Isolate;

...

#[no_mangle]
pub extern "C" fn load_page(port: i64, url: *const raw::c_char) -> i32 {
    // get a ref to the runtime
    let rt = runtime!();
    let url = cstr!(url);
    rt.spawn(async move {
        // load the page and get the result back
        let result = scrap::load_page(url).await;
        // make a ref to an isolate using it's port
        let isolate = Isolate::new(port);
        // and sent it the `Rust's` result
        // no need to convert anything :)
        isolate.post(result);
    });
    1
}
좋습니다. binding.h을 생성하고 iOSAndroid을 위해 모든 내용을 구축한 후에 Dart단 FFI를 작성해야 합니다.아, 다시는 그러지 마세요.🤦‍♂️

지금 C헤드 파일에서 Dart FFI 바인딩을 생성할 수 있다고 알려주면?

dart bindgen:dart FFI를 작성하는 새로운 방법


dart-bindgen은 Dart FFI를 C 헤더 파일에 연결하는 도구로 CLILibrary으로 나뉜다.
우리는 그것을 사용하여 외국 금융 기구를 생성하는 데 도움을 줄 것입니다. 단지 그것을 당신의 build-dependencies에 추가하기만 하면 됩니다
[build-dependencies]
cbindgen = "0.14.2" # Rust -> C header file
dart-bindgen = "0.1" # C header file -> Dart FFI
저희 build.rs에서.
use dart_bindgen::{config::*, Codegen};

fn main() {
    ...
    let config = DynamicLibraryConfig {
        ios: DynamicLibraryCreationMode::Executable.into(),
        android: DynamicLibraryCreationMode::open("libscrap_ffi.so").into(),
        ..Default::default()
    };
    // load the c header file, with config and lib name
    let codegen = Codegen::builder()
        .with_src_header("binding.h")
        .with_lib_name("libscrap")
        .with_config(config)
        .build()
        .unwrap();
    // generate the dart code and get the bindings back
    let bindings = codegen.generate().unwrap();
    // write the bindings to your dart package
    // and start using it to write your own high level abstraction.
    bindings
        .write_to_file("../../packages/scrap_ffi/lib/ffi.dart")
        .unwrap();
}
지금까지는 ffi.dart 파일에 더 높은 수준의 추상을 작성해 봤습니다.
// packages/scrap_ffi/lib/scrap.dart

import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// isolate package help us creating isolate and getting the port back easily.
import 'package:isolate/ports.dart';

import 'ffi.dart' as native;

class Scrap {
  // this only should be called once at the start up.
  static setup() {
    // give rust `allo-isolate` package a ref to the `NativeApi.postCObject` function.
    native.store_dart_post_cobject(NativeApi.postCObject);
    print("Scrap Setup Done");
  }

  Future<String> loadPage(String url) {
    var urlPointer = Utf8.toUtf8(url);
    final completer = Completer<String>();
    // Create a SendPort that accepts only one message.
    final sendPort = singleCompletePort(completer);
    final res = native.load_page(
      sendPort.nativePort,
      urlPointer,
    );
    if (res != 1) {
      _throwError();
    }
    return completer.future;
  }

  void _throwError() {
    final length = native.last_error_length();
    final Pointer<Utf8> message = allocate(count: length);
    native.error_message_utf8(message, length);
    final error = Utf8.fromUtf8(message);
    print(error);
    throw error;
  }
}
우리의 진동 응용 프로그램에서 우리는 이 소프트웨어 패키지를 사용할 수 있다.
...
class _MyHomePageState extends State<MyHomePage> {

  ...
  Scrap scrap;
  @override
  void initState() {
    super.initState();
    scrap = Scrap();
    Scrap.setup();
  }  

  // somewhere in your app
  final html = await scrap.loadPage('https://www.rust-lang.org/');
Android Emulator 또는 iOS 에뮬레이터를 시작하고 다음을 실행합니다.
$ cargo make # build all rust packages for iOS and Android
그리고
$ flutter run # 🔥
모든 코드는 flutterust에서 사용할 수 있으며, 이것은 유일한 복제와 해커이다😀.
Dart and Rust에 있는 다른 해커들을 계속 팔로우하세요. 저를 따라오시면 github을 클릭하세요.
정말 감사합니다.💚.

좋은 웹페이지 즐겨찾기