Penulisan Metode Asinkron
Topik ini menjelaskan cara menerapkan metode asinkron di Microsoft Media Foundation.
Metode asinkron banyak digunakan dalam jalur 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 penampung media, sangat penting untuk menangani operasi asinkron dengan benar, karena performa komponen Anda berdampak pada seluruh jalur.
Nota
Transformasi Media Foundation (MFTs) menggunakan metode sinkron secara bawaan.
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:
Terapkan antarmuka IMFAsyncCallback.
Panggil MFCreateAsyncResult untuk membuat objek hasil. Objek hasil mengekspos IMFAsyncResult. Objek hasil berisi tiga pointer:
- Penunjuk ke pemanggil antarmuka IMFAsyncCallback.
- Penunjuk opsional ke objek status. Jika ditentukan, objek status harus menerapkan IUnknown.
- Penunjuk opsional ke objek privat. Jika ditentukan, objek ini juga harus menerapkan IUnknown.
Dua pointer terakhir dapat null. Jika tidak, gunakan mereka untuk menyimpan informasi tentang operasi asinkron.
Alur antrean kerja memanggil metode IMFAsyncCallback::Invoke Anda.
Lakukan pekerjaan di dalam metode Invoke Anda. Parameter pAsyncResult dari metode ini adalah IMFAsyncResult pointer dari langkah 2. Gunakan penunjuk ini untuk mendapatkan objek status dan objek privat:
- Untuk mendapatkan objek status, panggil IMFAsyncResult::GetState.
- Untuk mendapatkan objek privat, panggil IMFAsyncResult::GetObject.
Sebagai alternatif, Anda dapat menggabungkan langkah 2 dan 3 dengan memanggil fungsiMFPutWorkItem. Secara internal, fungsi ini memanggil MFCreateAsyncResult untuk membuat objek hasil.
Diagram berikut menunjukkan hubungan antara pemanggil, objek hasil, objek status, dan objek privat.
diagram
Diagram urutan berikut menunjukkan cara objek mengantrekan item kerja. Saat utas antrean kerja memanggil Invoke, objek melakukan operasi asinkron pada utas itu.
diagram
Penting untuk diingat Invoke dipanggil dari utas yang dikelola oleh antrian pekerjaan. Implementasi Invoke Anda harus aman untuk dijalankan oleh banyak thread. Selain itu, jika Anda menggunakan antrean kerja platform (MFASYNC_CALLBACK_QUEUE_STANDARD), sangatlah penting untuk tidak pernah memblokir utas, karena hal itu dapat memblokir seluruh alur Media Foundation dari memproses data. Jika Anda perlu melakukan operasi yang akan memblokir atau membutuhkan waktu lama untuk diselesaikan, gunakan antrean kerja pribadi. 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.
Menerapkan Mulai.../Akhiri... Pola
Seperti yang dijelaskan dalam Memanggil Metode Asinkron, metode-metode asinkron di Media Foundation sering kali menggunakan pola Begin.../End..... 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 ini benar-benar asinkron, implementasi BeginX harus melakukan tugas sebenarnya pada utas lain. Di sinilah antrean kerja masuk ke dalam gambar. Dalam langkah-langkah berikut, penelepon adalah kode yang memanggil BeginX dan EndX. Ini mungkin merupakan sebuah aplikasi atau alur Media Foundation. Komponen adalah kode yang mengimplementasikan BeginX dan EndX.
- Pemanggil melakukan pemanggilan Begin..., dengan meneruskan pointer ke antarmuka IMFAsyncCallback milik pemanggil.
- 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.
- 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.
- Komponen memanggil MFPutWorkItemEx untuk mengantrekan item kerja baru.
- Dalam metode Invoke, komponen melakukan pekerjaan asinkron.
- Komponen memanggil MFInvokeCallback untuk memanggil metode panggilan balik pemanggil.
- Pemanggil memanggil metode EndX.
diagram
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 xBeginSquareRoot
adalah nilai yang akar kuadratnya akan dihitung. Akar kuadrat dikembalikan pada parameter pVal dari EndSquareRoot
.
Berikut adalah deklarasi kelas yang mengimplementasikan 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 SqrRoot
mengimplementasikan IMFAsyncCallback sehingga dapat menempatkan operasi akar kuadrat pada antrean kerja. Metode DoCalculateSquareRoot
adalah metode kelas privat yang menghitung akar kuadrat. Metode ini akan dipanggil dari thread antrean kerja.
Pertama, kita memerlukan cara untuk menyimpan nilai dari x, sehingga dapat diperoleh kembali ketika utas antrean kerja memanggil SqrRoot::Invoke
. 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 metode BeginSquareRoot
:
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 ini melakukan hal berikut:
- Membuat instans baru dari kelas
AsyncOp
untuk menyimpan nilai x. - Memanggil MFCreateAsyncResult untuk membuat objek hasil. Objek ini menyimpan beberapa penunjuk:
- Memanggil MFPutWorkItem untuk mengantrekan item kerja baru. Panggilan ini secara implisit membuat objek hasil luar, yang menyimpan pointer berikut:
- Penunjuk ke antarmuka IMFAsyncCallbackdari objek
SqrRoot
. - Penunjuk ke objek hasil dalam dari langkah 2.
- Penunjuk ke antarmuka IMFAsyncCallbackdari objek
Kode berikut mengimplementasikan metode SqrRoot::Invoke
:
// 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 internal dan objek AsyncOp
. Kemudian melewati objek AsyncOp
ke DoCalculateSquareRoot
. Terakhir, ini memanggil IMFAsyncResult::SetStatus untuk mengatur kode status dan MFInvokeCallback untuk memanggil metode panggilan balik penelepon.
Metode DoCalculateSquareRoot
melakukan apa yang Anda harapkan:
HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
pOp->m_value = sqrt(pOp->m_value);
return S_OK;
}
Ketika metode panggilan balik milik penelepon dipanggil, menjadi tanggung jawab penelepon untuk memanggil metode End...—dalam hal ini, EndSquareRoot
.
EndSquareRoot
adalah bagaimana pemanggil mengambil hasil dari operasi asinkron, yang dalam contoh ini adalah akar kuadrat yang dihitung. 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
sementara panggilan sebelumnya ke metode yang sama masih tertunda. Kelas SqrRoot
mungkin mengantrekan item kerja baru sebelum item kerja sebelumnya selesai. Namun, antrean kerja tidak dijamin untuk memproses item kerja secara berurutan. Ingat bahwa antrean kerja dapat menggunakan lebih dari satu thread untuk menjalankan item kerja. Di lingkungan multithread, item kerja mungkin dipanggil sebelum item 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 selesai sebelum operasi B dapat dimulai, objek tidak boleh mengantre item kerja untuk B hingga 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 menerapkan 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 marshaling COM untuk memindahkan penunjuk antarmuka melintasi batas utas. Oleh karena itu, bahkan jika objek terdaftar sebagai objek berulir-apartemen atau utas aplikasi telah memasuki apartemen berulir tunggal (STA), panggilan balik IMFAsyncCallback akan dipanggil melalui utas yang berbeda. Bagaimanapun, semua komponen alur Media Foundation harus menggunakan model threading "Both".
Beberapa antarmuka di Media Foundation mendefinisikan versi jarak jauh dari beberapa metode asinkron. Ketika salah satu metode ini dipanggil melintasi batas proses, DLL proksi/stub Media Foundation memanggil versi jarak jauh dari metode tersebut, yang melakukan pemanggilan khusus pada parameter dari metode tersebut. Dalam proses jarak jauh, stub mengonversi panggilan kembali menjadi 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.
Topik terkait