Bagikan melalui


Menulis Metode Asinkron

Topik ini menjelaskan cara menerapkan metode asinkron di Microsoft Media Foundation.

Metode asinkron di mana-mana dalam alur Media Foundation. Metode asinkron memudahkan untuk mendistribusikan pekerjaan di antara beberapa utas. Sangat penting untuk melakukan I/O secara asinkron, sehingga membaca dari file atau jaringan tidak memblokir sisa alur.

Jika Anda menulis sumber media atau sink media, sangat penting untuk menangani operasi asinkron dengan benar, karena performa komponen Anda berdampak pada seluruh alur.

Catatan

Transformasi Media Foundation (MFTs) menggunakan metode sinkron secara default.

 

Antrean Kerja Untuk Operasi Asinkron

Di Media Foundation, ada hubungan dekat antara Metode Panggilan Balik Asinkron dan Antrean Kerja. Antrean kerja adalah abstraksi untuk memindahkan pekerjaan dari utas pemanggil ke utas pekerja. Untuk melakukan pekerjaan pada antrean kerja, lakukan hal berikut:

  1. Menerapkan antarmuka IMFAsyncCallback .

  2. Panggil MFCreateAsyncResult untuk membuat objek hasil . Objek hasil mengekspos IMFAsyncResult. Objek hasil berisi tiga pointer:

    • Penunjuk ke antarmuka IMFAsyncCallback penelepon.
    • Penunjuk opsional ke objek status. Jika ditentukan, objek status harus mengimplementasikan IUnknown.
    • Penunjuk opsional ke objek privat. Jika ditentukan, objek ini juga harus mengimplementasikan IUnknown.

    Dua pointer terakhir bisa NULL. Jika tidak, gunakan untuk menyimpan informasi tentang operasi asinkron.

  3. Panggil MFPutWorkItemEx untuk mengantre ke item kerja.

  4. Utas antrean kerja memanggil metode IMFAsyncCallback::Invoke Anda.

  5. Lakukan pekerjaan di dalam metode Panggil Anda. Parameter pAsyncResult dari metode ini adalah penunjuk IMFAsyncResult dari langkah 2. Gunakan penunjuk ini untuk mendapatkan objek status dan objek privat:

Sebagai alternatif, Anda dapat menggabungkan langkah 2 dan 3 dengan memanggil fungsi MFPutWorkItem . Secara internal, fungsi ini memanggil MFCreateAsyncResult untuk membuat objek hasil.

Diagram berikut menunjukkan hubungan antara pemanggil, objek hasil, objek status, dan objek privat.

diagram memperlihatkan objek hasil asinkron

Diagram urutan berikut menunjukkan bagaimana objek mengantre item kerja. Ketika utas antrean kerja memanggil Invoke, objek melakukan operasi asinkron pada utas tersebut.

diagram memperlihatkan bagaimana objek mengantre item kerja

Penting untuk diingat Invoke dipanggil dari utas yang dimiliki oleh antrean kerja. Implementasi Invoke Anda harus aman untuk utas. Selain itu, jika Anda menggunakan antrean kerja platform (MFASYNC_CALLBACK_QUEUE_STANDARD), sangat penting bahwa Anda tidak pernah memblokir utas, karena itu dapat memblokir seluruh alur Media Foundation agar tidak memproses data. Jika Anda perlu melakukan operasi yang akan memblokir atau membutuhkan waktu lama untuk diselesaikan, gunakan antrean kerja privat. Untuk membuat antrean kerja privat, panggil MFAllocateWorkQueue. Setiap komponen alur yang melakukan operasi I/O harus menghindari pemblokiran panggilan I/O karena alasan yang sama. Antarmuka IMFByteStream menyediakan abstraksi yang berguna untuk I/O file asinkron.

Mengimplementasikan Mulai.../Akhir... Pola

Seperti yang dijelaskan dalam Memanggil Metode Asinkron, metode asinkron di Media Foundation sering menggunakan Begin.../Akhir.... Pola. Dalam pola ini, operasi asinkron menggunakan dua metode dengan tanda tangan yang mirip dengan yang berikut ini:

// Starts the asynchronous operation.
HRESULT BeginX(IMFAsyncCallback *pCallback, IUnknown *punkState);

// Completes the asynchronous operation. 
// Call this method from inside the caller's Invoke method.
HRESULT EndX(IMFAsyncResult *pResult);

Untuk membuat metode benar-benar asinkron, implementasi BeginX harus melakukan pekerjaan aktual pada utas lain. Di sinilah antrean kerja masuk ke dalam gambar. Dalam langkah-langkah berikut, pemanggil adalah kode yang memanggil BeginX dan EndX. Ini mungkin aplikasi atau alur Media Foundation. Komponen adalah kode yang mengimplementasikan BeginX dan EndX.

  1. Penelepon memanggil Mulai... , meneruskan penunjuk ke antarmuka IMFAsyncCallback penelepon.
  2. Komponen membuat objek hasil asinkron baru. Objek ini menyimpan antarmuka panggilan balik penelepon dan objek status. Biasanya, ia juga menyimpan informasi status privat apa pun yang dibutuhkan komponen untuk menyelesaikan operasi. Objek hasil dari langkah ini diberi label "Hasil 1" dalam diagram berikutnya.
  3. Komponen membuat objek hasil kedua. Objek hasil ini menyimpan dua pointer: Objek hasil pertama, dan antarmuka panggilan balik penerima panggilan. Objek hasil ini diberi label "Hasil 2" dalam diagram berikutnya.
  4. Komponen memanggil MFPutWorkItemEx untuk mengantre item kerja baru.
  5. Dalam metode Panggil , komponen melakukan pekerjaan asinkron.
  6. Komponen memanggil MFInvokeCallback untuk memanggil metode panggilan balik pemanggil.
  7. Pemanggil memanggil metode EndX .

diagram memperlihatkan bagaimana objek mengimplementasikan pola awal/akhir

Contoh Metode Asinkron

Untuk mengilustrasikan diskusi ini, kita akan menggunakan contoh yang disusun. Pertimbangkan metode asinkron untuk menghitung akar kuadrat:

    HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
    HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);

Parameter x adalah BeginSquareRoot nilai yang akar kuadratnya akan dihitung. Akar kuadrat dikembalikan dalam parameter pVal .EndSquareRoot

Berikut adalah deklarasi kelas yang menerapkan dua metode ini:

class SqrRoot : public IMFAsyncCallback
{
    LONG    m_cRef;
    double  m_sqrt;

    HRESULT DoCalculateSquareRoot(AsyncOp *pOp);

public:

    SqrRoot() : m_cRef(1)
    {

    }

    HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
    HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);

    // IUnknown methods.
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] = 
        {
            QITABENT(SqrRoot, IMFAsyncCallback),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        LONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }

    // IMFAsyncCallback methods.

    STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
    {
        // Implementation of this method is optional.
        return E_NOTIMPL;  
    }
    // Invoke is where the work is performed.
    STDMETHODIMP Invoke(IMFAsyncResult* pResult);
};

Kelas mengimplementasikan SqrRootIMFAsyncCallback sehingga dapat menempatkan operasi akar kuadrat pada antrean kerja. Metode DoCalculateSquareRoot ini adalah metode kelas privat yang menghitung akar kuadrat. Metode ini akan dipanggil dari utas antrean kerja.

Pertama, kita memerlukan cara untuk menyimpan nilai x, sehingga dapat diambil ketika utas SqrRoot::Invokeantrean kerja memanggil . Berikut adalah kelas sederhana yang menyimpan informasi:

class AsyncOp : public IUnknown
{
    LONG    m_cRef;

public:

    double  m_value;

    AsyncOp(double val) : m_cRef(1), m_value(val) { }

    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] = 
        {
            QITABENT(AsyncOp, IUnknown),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        LONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }
};

Kelas ini mengimplementasikan IUnknown sehingga dapat disimpan dalam objek hasil.

Kode berikut mengimplementasikan BeginSquareRoot metode :

HRESULT SqrRoot::BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState)
{
    AsyncOp *pOp = new (std::nothrow) AsyncOp(x);
    if (pOp == NULL)
    {
        return E_OUTOFMEMORY;
    }

    IMFAsyncResult *pResult = NULL;

    // Create the inner result object. This object contains pointers to:
    // 
    //   1. The caller's callback interface and state object. 
    //   2. The AsyncOp object, which contains the operation data.
    //

    HRESULT hr = MFCreateAsyncResult(pOp, pCB, pState, &pResult);

    if (SUCCEEDED(hr))
    {
        // Queue a work item. The work item contains pointers to:
        // 
        // 1. The callback interface of the SqrRoot object.
        // 2. The inner result object.

        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, this, pResult);

        pResult->Release();
    }

    return hr;
}

Kode melakukan hal berikut:

  1. Membuat instans AsyncOp baru kelas untuk menahan nilai x.
  2. Memanggil MFCreateAsyncResult untuk membuat objek hasil. Objek ini menyimpan beberapa pointer:
    • Penunjuk ke antarmuka IMFAsyncCallback penelepon.
    • Penunjuk ke objek status pemanggil (pState).
    • Penunjuk ke AsyncOp objek .
  3. Memanggil MFPutWorkItem untuk mengantre item kerja baru. Panggilan ini secara implisit membuat objek hasil luar, yang menyimpan pointer berikut:
    • Penunjuk ke SqrRoot antarmuka IMFAsyncCallback objek.
    • Penunjuk ke objek hasil dalam dari langkah 2.

Kode berikut mengimplementasikan SqrRoot::Invoke metode :

// Invoke is called by the work queue. This is where the object performs the
// asynchronous operation.

STDMETHODIMP SqrRoot::Invoke(IMFAsyncResult* pResult)
{
    HRESULT hr = S_OK;

    IUnknown *pState = NULL;
    IUnknown *pUnk = NULL;
    IMFAsyncResult *pCallerResult = NULL;

    AsyncOp *pOp = NULL; 

    // Get the asynchronous result object for the application callback. 

    hr = pResult->GetState(&pState);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pState->QueryInterface(IID_PPV_ARGS(&pCallerResult));
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the object that holds the state information for the asynchronous method.
    hr = pCallerResult->GetObject(&pUnk);
    if (FAILED(hr))
    {
        goto done;
    }

    pOp = static_cast<AsyncOp*>(pUnk);

    // Do the work.

    hr = DoCalculateSquareRoot(pOp);

done:
    // Signal the application.
    if (pCallerResult)
    {
        pCallerResult->SetStatus(hr);
        MFInvokeCallback(pCallerResult);
    }

    SafeRelease(&pState);
    SafeRelease(&pUnk);
    SafeRelease(&pCallerResult);
    return S_OK;
}

Metode ini mendapatkan objek hasil dalam dan AsyncOp objek . Kemudian meneruskan objek ke AsyncOpDoCalculateSquareRoot. Terakhir, ia memanggil IMFAsyncResult::SetStatus untuk mengatur kode status dan MFInvokeCallback untuk memanggil metode panggilan balik pemanggil.

Metode ini DoCalculateSquareRoot melakukan persis apa yang Anda harapkan:

HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
    pOp->m_value = sqrt(pOp->m_value);

    return S_OK;
}

Ketika metode panggilan balik penelepon dipanggil, pemanggil bertanggung jawab untuk memanggil metode Akhir... — dalam hal ini, EndSquareRoot. EndSquareRoot adalah bagaimana pemanggil mengambil hasil operasi asinkron, yang dalam contoh ini adalah akar kuadrat komputasi. Informasi ini disimpan dalam objek hasil:

HRESULT SqrRoot::EndSquareRoot(IMFAsyncResult *pResult, double *pVal)
{
    *pVal = 0;

    IUnknown *pUnk = NULL;

    HRESULT hr = pResult->GetStatus();

    if (FAILED(hr))
    {
        goto done;
    }

    hr = pResult->GetObject(&pUnk);
    if (FAILED(hr))
    {
        goto done;
    }

    AsyncOp *pOp = static_cast<AsyncOp*>(pUnk);

    // Get the result.
    *pVal = pOp->m_value;

done:
    SafeRelease(&pUnk);
    return hr;
}

Antrean Operasi

Sejauh ini, secara taktik diasumsikan bahwa operasi asinkron dapat dilakukan kapan saja, terlepas dari status objek saat ini. Misalnya, pertimbangkan apa yang terjadi jika aplikasi memanggil BeginSquareRoot saat panggilan sebelumnya ke metode yang sama masih tertunda. Kelas SqrRoot mungkin mengantre item kerja baru sebelum item kerja sebelumnya selesai. Namun, antrean kerja tidak dijamin untuk menserialisasikan item kerja. Ingat bahwa antrean kerja dapat menggunakan lebih dari satu utas untuk mengirimkan item kerja. Di lingkungan multithreaded, item kerja mungkin dipanggil sebelum yang sebelumnya selesai. Item kerja bahkan dapat dipanggil secara tidak berurutan, jika pengalihan konteks terjadi tepat sebelum panggilan balik dipanggil.

Untuk alasan ini, objek bertanggung jawab untuk menserialisasikan operasi pada dirinya sendiri, jika diperlukan. Dengan kata lain, jika objek memerlukan operasi A untuk menyelesaikan sebelum operasi B dapat dimulai, objek tidak boleh mengantre item kerja untuk B sampai operasi A selesai. Objek dapat memenuhi persyaratan ini dengan memiliki antrean operasi tertunda sendiri. Ketika metode asinkron dipanggil pada objek , objek menempatkan permintaan pada antreannya sendiri. Saat setiap operasi asinkron selesai, objek menarik permintaan berikutnya dari antrean. Sampel MPEG1Source menunjukkan contoh cara mengimplementasikan antrean tersebut.

Satu metode mungkin melibatkan beberapa operasi asinkron, terutama ketika panggilan I/O digunakan. Saat Anda menerapkan metode asinkron, pikirkan dengan cermat tentang persyaratan serialisasi. Misalnya, apakah valid bagi objek untuk memulai operasi baru saat permintaan I/O sebelumnya masih tertunda? Jika operasi baru mengubah status internal objek, apa yang terjadi ketika permintaan I/O sebelumnya selesai dan mengembalikan data yang sekarang mungkin kedaluarsa? Diagram status yang baik dapat membantu mengidentifikasi transisi status yang valid.

Pertimbangan Lintas Alur dan Lintas Proses

Antrean kerja tidak menggunakan com marshaling untuk penunjuk antarmuka marshal di seluruh batas utas. Oleh karena itu, bahkan jika objek terdaftar sebagai rangkaian apartemen atau utas aplikasi telah memasuki apartemen berulir tunggal (STA), panggilan balik IMFAsyncCallback akan dipanggil dari utas yang berbeda. Bagaimanapun, semua komponen alur Media Foundation harus menggunakan model utas "Keduanya".

Beberapa antarmuka di Media Foundation mendefinisikan versi jarak jauh dari beberapa metode asinkron. Ketika salah satu metode ini dipanggil di seluruh batas proses, DLL proksi/stub Media Foundation memanggil versi jarak jauh metode , yang melakukan marsekal kustom parameter metode. Dalam proses jarak jauh, stub menerjemahkan panggilan kembali ke metode lokal pada objek . Proses ini transparan untuk aplikasi dan objek jarak jauh. Metode marshaling kustom ini disediakan terutama untuk objek yang dimuat di jalur media yang dilindungi (PMP). Untuk informasi selengkapnya tentang PMP, lihat Jalur Media Terproteksi.

Metode Panggilan Balik Asinkron

Antrean Kerja