Bài viết này là phần tiếp theo của loạt bài viết về: Xử lý bất đồng bộ trong phát triển Android và iOS. Trong phần thứ ba của tác phẩm nàyVSBET, chúng ta sẽ tập trung những vấn đề có thể phát sinh khi thực hiện đồng thời nhiều tác vụ bất đồng bộ. Việc quản lý và xử lý các tác vụ như vậy thường đòi hỏi kỹ năng đặc biệt để đảm bảo hiệu quả và độ tin cậy trong quá trình vận hành.
Thông thườngkết quả bóng đá việt nam hôm nay, chúng ta cần thực hiện nhiều tác vụ bất đồng bộ và khiến chúng phối hợp với nhau để hoàn thành yêu cầu. Bài viết này sẽ phân tích ba mối quan hệ phối hợp giữa các tác vụ bất đồng bộ dựa trên các tình huống sử dụng điển hình trong thực tế: 1. **Mối quan hệ tuần tự (Sequence):** Khi các tác vụ phải được thực hiện theo trình tự nhất định, chẳng hạn như tải dữ liệu từ một nguồn trước khi xử lý tiếp ở bước kế tiếp. 2. **Mối quan hệ song song (Parallel):** Trong trường hợp cần chạy nhiều tác vụ cùng lúc để tối ưu hóa hiệu suất, ví dụ như tải nhiều tệp tin cùng một lúc từ các máy chủ khác nhau. 3. **Mối quan hệ có điều kiện (Conditional):** Các tác vụ chỉ được kích hoạt khi một điều kiện nào đó đã đạt được, chẳng hạn như chờ đợi kết quả từ một tác vụ trước đó để quyết định có nên tiếp tục hay không. Hãy cùng tìm hiểu sâu hơn về từng loại mối quan hệ này nhé!
Ba mối quan hệ hợp tác trên sẽ được thảo luận chi tiết qua ba ví dụ ứng dụng cụ thể trong bài viết này. Ba ứng dụng này bao gồm:
yêu cầu mạng song song
Lưu ý: Mã nguồn trong loạt bài viết này đã được tổ chức trên GitHub (liên tục cập nhật)keo banh, đường dẫn kho lưu trữ là:
Trong bài viết nàyVSBET, đoạn mã Java được đề cập nằm trong gói (package) có tên là `com. demos.async. multitask`. Đây là một phần quan trọng của chương trình, giúp người đọc hiểu rõ hơn về cách triển khai lập trình đa nhiệm trong môi trường đồng bộ và bất đồng bộ.
Thực hiện tuần tự
Một ví dụ điển hình là việc đa cấp bộ nhớ đệm cho tài nguyên tĩnhVSBET, trong đó người ta thường nhắc đến nhiều nhất chính là việc đa cấp bộ nhớ đệm cho hình ảnh tĩnh. Khi tải một hình ảnh tĩnh trên client, thông thường sẽ có ít nhất hai cấp bộ nhớ đệm: bộ nhớ đệm đầu tiên là Memory Cache (bộ nhớ cache trong bộ nhớ RAM) và bộ nhớ đệm thứ hai là Disk Cache (bộ nhớ cache trên ổ cứng). Quy trình tải hình ảnh này diễn ra như sau: Trước hết, khi người dùng yêu cầu tải một hình ảnh tĩnh, hệ thống sẽ kiểm tra Memory Cache trước tiên. Nếu hình ảnh đã tồn tại trong bộ nhớ đệm RAM, nó sẽ được trả về ngay lập tức mà không cần truy cập đến ổ đĩa hoặc nguồn gốc ban đầu. Điều này giúp tăng tốc đáng kể quá trình tải, đặc biệt đối với những hình ảnh thường xuyên được sử dụng. Nếu hình ảnh không có sẵn trong bộ nhớ RAM, hệ thống sẽ tiếp tục kiểm tra ở Ở cấp độ này, hình ảnh sẽ được lưu trữ tạm thời trên ổ cứng của thiết bị. Việc kiểm tra Disk Cache nhanh hơn so với việc tải hình ảnh trực tiếp từ máy chủ, vì vậy nó vẫn mang lại hiệu quả cao hơn so với việc tải hoàn toàn mới. Chỉ khi cả Memory Cache và Disk Cache đều không chứa hình ảnh cần tìm, hệ thống mới tiến hành tải hình ảnh từ nguồn gốc ban đầu như một server hoặc API. Điều này giúp tối ưu hóa hiệu suất và giảm thiểu sự phụ thuộc vào băng thông mạng, đồng thời cải thiện trải nghiệm người dùng.
thực hiện yêu cầu mạng
tìm kiếm Disk Cache
Trước tiênkết quả bóng đá việt nam hôm nay, chúng ta cần xác định rõ giao diện cho hai tác vụ bất đồng bộ là "Bộ đệm Disk" và "Yêu cầu mạng". Điều này sẽ giúp chúng ta tổ chức mã nguồn một cách có trật tự và dễ bảo trì hơn. Hai tác vụ này đóng vai trò quan trọng trong việc tối ưu hóa hiệu suất ứng dụng của chúng ta.
public
interface
ImageDiskCache
{
/** * Lấy đồng bộ hình ảnh Bitmap từ bộ nhớ đệm với khóa được chỉ định. * @param key Chuỗi khóa để tìm kiếm trong bộ nhớ đệm. * @param callback Hàm trả về đối tượng Bitmap được lưu trữ trong bộ nhớ đệm. */
void
getImage
(
String
key
,
AsyncCallback
<
Bitmap
>
callback
);
/** * Lưu đối tượng Bitmap vào bộ nhớ đệm. * @param key Chuỗi duy nhất để xác định vị trí lưu trữ trong bộ nhớ đệm. * @param bitmap Đối tượng Bitmap cần được lưu giữ. * @param callback Hàm trả về kết quả của hoạt động lưu trữkeo banh, cho biết thành công hay thất bại. */
void
putImage
(
String
key
,
Bitmap
bitmap
,
AsyncCallback
<
Boolean
>
callback
);
}
Giao diện ImageDiskCache được sử dụng để lưu trữ và truy xuất Cache hình ảnh trên đĩakết quả bóng đá việt nam hôm nay, trong đó tham số AsyncCallback là định nghĩa của một giao diện gọi lại bất đồng bộ chung. Mã định nghĩa của nó như sau (sẽ còn được đề cập đến ở phần sau): interface AsyncCallback
/**<d>Kiểu dữ liệu tham số trả về từ giao diện bất đồng bộ.</d>
public
interface
AsyncCallback
<
D
>
{
void
onResult
(
D
data
);
}
Còn việc tải xuống tệp hình ảnh qua mạngkeo banh, chúng ta có thể gọi trực tiếp từ bài viết trước về: Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Gọi lại cho tác vụ bất đồng bộ Trong tài liệu đã đề cập đến giao diện Downloader (chú ý: phiên bản của giao diện Downloader có tham số contextData ở cuối).
Phát yêu cầu tải xuống mạng
//Kiểm tra bộ nhớ đệm thứ hai: bộ nhớ đệm Disk
imageDiskCache
.
getImage
(
url
,
new
AsyncCallback
<
Bitmap
>()
{
@Override
public
void
onResult
(
Bitmap
bitmap
)
{
if
(
bitmap
!=
null
)
{
//Đã trúng bộ nhớ đệm DiskVSBET, nhiệm vụ tải về kết thúc sớm.
imageMemCache
.
putImage
(
url
,
bitmap
);
successCallback
(
url
,
bitmap
,
contextData
);
}
else
{
//Không trúng cả hai cấp bộ nhớ đệmVSBET, gọi tải về để tải xuống
downloader
.
startDownload
(
url
,
getLocalPath
(
url
),
contextData
);
}
}
});
Ví dụ mã thực hiện callback kết quả thành công của Downloader như sau:
@Override
public
void
downloadSuccess
(
final
String
url
,
final
String
localPath
,
final
Object
contextData
)
{
//Giải mã hình ảnhVSBET, đây là một hoạt động tiêu tốn thời gian, nên làm bất đồng bộ.
imageDecodingExecutor
.
execute
(
new
Runnable
()
{
@Override
public
void
run
()
{
final
Bitmap
bitmap
=
decodeBitmap
(
new
File
(
localPath
));
//Sắp xếp lại vào luồng chính.
mainHandler
.
post
(
new
Runnable
()
{
@Override
public
void
run
()
{
if
(
bitmap
!=
null
)
{
imageMemCache
.
putImage
(
url
,
bitmap
);
imageDiskCache
.
putImage
(
url
,
bitmap
,
null
);
successCallback
(
url
,
bitmap
,
contextData
);
}
else
{
//Giải mã thất bại
failureCallback
(
url
,
ImageLoaderListener
.
BITMAP_DECODE_FAILED
,
contextData
);
}
}
});
}
});
}
Thực thi đồng thờikết quả bóng đá việt nam hôm nay, hợp nhất kết quả
Một ví dụ điển hình là khi bạn gửi đồng thời nhiều yêu cầu mạng (các giao diện API từ xa)kết quả bóng đá việt nam hôm nay, sau đó đợi tất cả các phản hồi từ các yêu cầu này và xử lý dữ liệu một cách đồng bộ trước khi cập nhật giao diện người dùng. Cách làm này giúp giảm thiểu tổng thời gian yêu cầu bằng cách tận dụng tính năng đa luồng trong việc gửi yêu cầu mạng. Điều thú vị là, thay vì chờ từng yêu cầu hoàn thành riêng lẻ, việc thực hiện song song không chỉ cải thiện hiệu suất mà còn tạo ra trải nghiệm mượt mà hơn cho người dùng.
Chúng tôi sẽ đưa ra ví dụ mã nguồn dựa trên tình huống đơn giản nhất của hai yêu cầu mạ
Trước tiênkeo banh, vẫn cần định nghĩa trước các giao diện bất đồng bộ cần thiết, tức là định nghĩa giao diện API từ xa.
/**
public
interface
HttpService
{
/** * Gửi yêu cầu HTTP. * @param apiUrl Đường URL yêu cầu * @param request Đối tượng tham số yêu cầu (được biểu diễn bằng Java Bean) * @param listener Bộ xử lý sự kiện * @param contextData Dữ liệu truyền thông giữa các lớp * @param */<t>Kiểu Model yêu cầu<r>Kiểu Model phản hồi</r></t>
<
T
,
R
>
void
doRequest
(
String
apiUrl
,
T
request
,
HttpListener
<?
super
T
,
R
>
listener
,
Object
contextData
);
}
/**<t>Kiểu Model yêu cầu<r>Kiểu Model phản hồi</r></t>
public
interface
HttpListener
<
T
,
R
>
{
/** * Giao diện callback được kích hoạt khi yêu cầu kết thúc với trạng thái thành công hoặc thất bại. * @param apiUrl Đường dẫn URL của yêu cầu * @param request Đối tượng chứa thông tin chi tiết về yêu cầu * @param result Kết quả cuối cùng của yêu cầu (bao gồm phản hồi hoặc lý do lỗi) * @param contextData Dữ liệu truyền tải phụ trợ giữ các lần gọi */
void
onResult
(
String
apiUrl
,
T
request
,
HttpResult
<
R
>
result
,
Object
contextData
);
}
Điều cần lưu ý là trong giao diện HttpServicekeo banh, tham số yêu cầu request được định nghĩa bằng kiể Nếu có một thực hiện cho giao diện này, mã thực hiện sẽ cần xử lý dựa trên loại thực tế của request (có thể là bất kỳ Java Bean nào), sử dụng cơ chế phản chiếu để chuyển đổi nó thành các tham số yêu cầu HTTP. Tất nhiên, ở đây chúng ta chỉ tập trung vào việc phân tích giao diện, còn việc cụ thể hóa cách triển khai không phải là trọng tâm thảo luận tại thời điểm này.
Tham số kết quả trả về result là kiểu HttpResultkết quả bóng đá việt nam hôm nay, điều này cho phép nó vừa có thể biểu thị kết quả phản hồi thành công, vừa có thể biểu thị kết quả phản hồi thất bại. Định nghĩa của HttpResult như sau: ``` class HttpResult { boolean isSuccess; String message; Object data; HttpResult(boolean isSuccess, String message, Object data) { isSuccess = isSuccess; message = message; data = data; } // Getter và Setter cho các thuộc tính public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean success) { isSuccess = success; } public String getMessage() { return message; } public void setMessage(String message) { message = message; } public Object getData() { return data; } public void setData(Object data) { data = data; } } ``` Với cấu trúc này, HttpResult trở thành một công cụ linh hoạt để xử lý cả hai trường hợp kết quả trả về từ một yêu cầu HTTP.
/** * Lớp HttpResult được thiết kế để đóng gói kết quả của các yêu cầu HTTP. Khi máy chủ phản hồi thành côngVSBET, mã lỗi errorCode sẽ bằng với SUCCESS và nội dung phản hồi từ máy chủ sẽ được chuyển đổi thành đối tượ Ngược lại, nếu máy chủ không thể phản hồi đúng cách, mã lỗi errorCode sẽ khác SUCCESS và giá trị của response sẽ không hợp lệ. */Kiểu Model phản hồi
public
class
HttpResult
<
R
>
{
/**
public
static
final
int
SUCCESS
=
0
;
//Thành công
public
static
final
int
REQUEST_ENCODING_ERROR
=
1
;
//Lỗi khi mã hóa yêu cầu
public
static
final
int
RESPONSE_DECODING_ERROR
=
2
;
//Lỗi khi giải mã phản hồi
public
static
final
int
NETWORK_UNAVAILABLE
=
3
;
//Mạng không khả dụng
public
static
final
int
UNKNOWN_HOST
=
4
;
//Lỗi phân giải tên miền
public
static
final
int
CONNECT_TIMEOUT
=
5
;
//Hết thời gian kết nối
public
static
final
int
HTTP_STATUS_NOT_OK
=
6
;
//Yêu cầu tải xuống trả về mã khác 200
public
static
final
int
UNKNOWN_FAILED
=
7
;
//Lỗi chưa xác định khác
private
int
errorCode
;
private
String
errorMessage
;
/** * Trả về là phản hồi mà máy chủ gửi lại. * Chỉ khi errorCode được gán giá trị SUCCESS thì phản hồi trong response mới có giá trị hợp lệ. */
private
R
response
;
public
int
getErrorCode
()
{
return
errorCode
;
}
public
void
setErrorCode
(
int
errorCode
)
{
this
.
errorCode
=
errorCode
;
}
public
String
getErrorMessage
()
{
return
errorMessage
;
}
public
void
setErrorMessage
(
String
errorMessage
)
{
this
.
errorMessage
=
errorMessage
;
}
public
R
getResponse
()
{
return
response
;
}
public
void
setResponse
(
R
response
)
{
this
.
response
=
response
;
}
}
Kết quả HttpResult cũng bao gồm một kiểu Generic Rkết quả bóng đá việt nam hôm nay, đây chính là kiểu tham số phản hồi được trả về khi yêu cầu thành công. Tương tự, trong việc triển khai có thể của HttpService, cơ chế phản chiếu (reflection) sẽ lại được sử dụng để biến đổi nội dung phản hồi từ yêu cầu (có thể là chuỗi JSON) thành kiểu R (kiểu này có thể là bất kỳ đối tượng Java nào). Điều này giúp tăng tính linh hoạt và khả năng tùy chỉnh cho ứng dụng, đảm bảo rằng dữ liệu nhận được có thể dễ dàng được xử lý theo định dạng mong muốn mà không gặp bất kỳ trở ngại nào trong việc chuyển đổi loại dữ liệu.
Rồikeo banh, bây giờ khi đã có giao diện HttpService, chúng ta có thể trình diễn cách gửi đồng thời hai yêu cầu mạng. Điều này giúp tiết kiệm thời gian và tối ưu hóa hiệu suất, đặc biệt hữu ích khi cần xử lý nhiều tác vụ cùng lúc trên ứng dụng của bạn.
public
class
MultiRequestsDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
/**
private
Map
<
String
,
Object
>
httpResults
=
new
HashMap
<
String
,
Object
>();
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_multi_requests_demo
);
//Khởi động đồng thời hai yêu cầu bất đồng bộ
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest1
(),
new
HttpListener
<
HttpRequest1
,
HttpResponse1
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest1
request
,
HttpResult
<
HttpResponse1
>
result
,
Object
contextData
)
{
//Lưu trữ kết quả yêu cầu
httpResults
.
put
(
"request-1"
,
result
);
if
(
checkAllHttpResultsReady
())
{
//Cả hai yêu cầu đã kết thúc
HttpResult
<
HttpResponse1
>
result1
=
result
;
HttpResult
<
HttpResponse2
>
result2
=
(
HttpResult
<
HttpResponse2
>)
httpResults
.
get
(
"request-2"
);
if
(
checkAllHttpResultsSuccess
())
{
//Cả hai yêu cầu đều thành công
processData
(
result1
.
getResponse
(),
result2
.
getResponse
());
}
else
{
//Cả hai yêu cầu không hoàn toàn thành côngkeo banh, xử lý như thất bại
processError
(
result1
.
getErrorCode
(),
result2
.
getErrorCode
());
}
}
}
},
null
);
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest2
(),
new
HttpListener
<
HttpRequest2
,
HttpResponse2
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest2
request
,
HttpResult
<
HttpResponse2
>
result
,
Object
contextData
)
{
//Lưu trữ kết quả yêu cầu
httpResults
.
put
(
"request-2"
,
result
);
if
(
checkAllHttpResultsReady
())
{
//Cả hai yêu cầu đã kết thúc
HttpResult
<
HttpResponse1
>
result1
=
(
HttpResult
<
HttpResponse1
>)
httpResults
.
get
(
"request-1"
);
HttpResult
<
HttpResponse2
>
result2
=
result
;
if
(
checkAllHttpResultsSuccess
())
{
//Cả hai yêu cầu đều thành công
processData
(
result1
.
getResponse
(),
result2
.
getResponse
());
}
else
{
//Cả hai yêu cầu không hoàn toàn thành côngkết quả bóng đá việt nam hôm nay, xử lý như thất bại
processError
(
result1
.
getErrorCode
(),
result2
.
getErrorCode
());
}
}
}
},
null
);
}
/** * Kiểm tra xem tất cả các yêu cầu đã có kết quả hay chưa * @return */
private
boolean
checkAllHttpResultsReady
()
{
int
requestsCount
=
2
;
for
(
int
i
=
1
;
i
<=
requestsCount
;
i
++)
{
if
(
httpResults
.
get
(
"request-"
+
i
)
==
null
)
{
return
false
;
}
}
return
true
;
}
/** * Kiểm tra xem tất cả các yêu cầu có đều thành công hay không * @return */
private
boolean
checkAllHttpResultsSuccess
()
{
int
requestsCount
=
2
;
for
(
int
i
=
1
;
i
<=
requestsCount
;
i
++)
{
HttpResult
<? > result
=
(
HttpResult
<? >) httpResults
.
get
(
"request-"
+
i
);
if
(
result
==
null
||
result
.
getErrorCode
()
!=
HttpResult
.
SUCCESS
)
{
return
false
;
}
}
return
true
;
}
private
void
processData
(
HttpResponse1
data1
,
HttpResponse2
data2
)
{
//TODO: Cập nhật giao diện người dùngkeo banh, hiển thị kết quả yêu cầu. Code ở đây bị lược bỏ
}
private
void
processError
(
int
errorCode1
,
int
errorCode2
)
{
//TODO: Cập nhật giao diện người dùngkeo banh, hiển thị lỗi. Code ở đây bị lược bỏ
}
}
Trước tiênVSBET, chúng ta cần chờ cho đến khi cả hai yêu cầu hoàn tất trước khi có thể hợp nhất kết quả của chúng. Để xác định liệu hai yêu cầu bất đồng bộ này đã hoàn thành hay chưa, chúng ta phải kiểm tra xem tất cả các yêu cầu đã trả về hay chưa mỗi khi một trong những yêu cầu nhận được phản hồi. Điều quan trọng cần lưu ý ở đây là chúng ta có thể áp dụng phương pháp kiểm tra này nhờ vào một điều kiện tiên quyết rất quan trọng: onResult của HttpService đã được lên lịch để thực thi trên luồng chính. Trong bài viết trước, " Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Gọi lại cho tác vụ bất đồng bộ Trong phần “mô hình luồng của hàm callback” trong tài liệukết quả bóng đá việt nam hôm nay, đã có đề cập đến môi trường luồng nơi callback xảy ra. Khi onResult đã được lên lịch để chạy trên luồng chính, thứ tự thực thi của hai callback onResult từ hai yêu cầu sẽ chỉ có hai khả năng: hoặc là thực thi onResult của yêu cầu đầu tiên trước rồi mới đến onResult của yêu cầu thứ hai; hoặc ngược lại, thực thi onResult của yêu cầu thứ hai trước và sau đó mới đến yêu cầu đầu tiên. Dù theo thứ tự nào đi chăng nữa, các câu lệnh kiểm tra bên trong onResult vẫn sẽ hoạt động hiệu quả và chính xác. Điều này đảm bảo rằng việc xử lý dữ liệu không bị ảnh hưởng bởi sự khác biệt trong thứ tự thực thi giữa cá
Tuy nhiênkeo banh, nếu phương thức onResult của HttpService được thực hiện trên các luồng khác nhau, hai callback onResult của các yêu cầu có thể sẽ chạy xen kẽ, dẫn đến các vấn đề đồng bộ bên trong các phép kiểm tra và xử lý. Điều này có thể gây ra sự không ổn định khi dữ liệu từ các yêu cầu khác nhau bị trộn lẫn hoặc xử lý không đúng cách. Vì vậy, việc quản lý trạng thái và đồng bộ hóa giữa các luồng trở nên vô cùng quan trọng để tránh những hậu quả không mong muốn.
thực thi tuần tự
Thực hiện đồng thời với ưu tiên cho một bên
Một ví dụ điển hình là bộ nhớ đệm trang. Chẳng hạnkeo banh, một trang web cần hiển thị danh sách dữ liệu động. Nếu mỗi lần người dùng mở trang đều chỉ lấy danh sách dữ liệu từ máy chủ, thì trong trường hợp không có kết nối mạng hoặc mạng chậm, trang sẽ bị trống trong thời gian dài, khiến trải nghiệm người dùng trở nên tệ hại. Trong những tình huống như vậy, việc hiển thị dữ liệu cũ thường tốt hơn là để trang hoàn toàn trắng. Do đó, chúng ta thường nghĩ đến việc thêm vào cơ chế bộ nhớ đệm cục bộ để lưu trữ dữ liệu danh sách này một cách vĩnh viễn. Việc lưu trữ dữ liệu cục bộ không chỉ giúp cải thiện hiệu suất mà còn giảm tải cho máy chủ. Khi không có kết nối mạng hoặc khi người dùng ở vùng có tốc độ internet yếu, bộ nhớ đệm sẽ tự động cung cấp dữ liệu từ bộ nhớ trước đó. Tuy nhiên, cần phải cân nhắc rằng việc sử dụng bộ nhớ đệm cũng cần được quản lý cẩn thận, chẳng hạn như cập nhật dữ liệu thường xuyên và đảm bảo dữ liệu cũ không làm sai lệch thông tin quan trọng. Điều này giúp duy trì sự chính xác và tính năng động của trang web trong mọi tình huống.
Bộ nhớ đệm cục bộ cũng là một nhiệm vụ bất đồng bộkeo banh, giao diện mã định nghĩa như sau:
public
interface
LocalDataCache
{
/** * Lấy đồng bộ đối tượng HttpResponse từ bộ nhớ đệm cục bộ. * @param key Khóa để xác định vị trí của đối tượng trong bộ nhớ đệm * @param callback Hàm trả về đối tượng đã lưu trữ từ bộ nhớ đệm */
void
getCachingData
(
String
key
,
AsyncCallback
<
HttpResponse
>
callback
);
/** * Lưu đối tượng HttpResponse vào bộ nhớ cache. * @param key Chuỗi duy nhất để xác định vị trí lưu trữ * @param data Đối tượng HttpResponse cần được lưu giữ. * @param callback Hàm trả về kết quả hoạt động lưu trữkết quả bóng đá việt nam hôm nay, cho biết thành công hay thất bại. */
void
putCachingData
(
String
key
,
HttpResponse
data
,
AsyncCallback
<
Boolean
>
callback
);
}
Dữ liệu được lưu trong bộ nhớ cache cục bộ này chính là đối tượng HttpResponse mà trước đó đã được lấy từ máy chủ. Giao diện gọi lại bất đồng bộ AsyncCallbackkeo banh, chúng ta đã đề cập đến điều này ở phần trước. Ngoài ra, việc sử dụng bộ nhớ cache giúp tối ưu hóa hiệu suất, đặc biệt khi dữ liệu cần được truy xuất nhanh chóng mà không cần phải liên tục yêu cầu từ máy chủ.
Khi trang web được mởkeo banh, chúng ta có thể khởi động đồng thời nhiệm vụ đọc từ bộ nhớ đệm cục bộ và nhiệm vụ yêu cầu API từ xa. Trong đó, nhiệm vụ thứ hai sẽ có ưu tiên cao hơn so với nhiệm vụ đầu tiên. Điều này giúp đảm bảo rằng dữ liệu quan trọng từ nguồn bên ngoài sẽ được xử lý nhanh chóng trước khi tiếp tục với các hoạt động khác.
public
class
PageCachingDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
private
LocalDataCache
localDataCache
=
new
MockLocalDataCache
();
/**
private
boolean
dataFromHttpReady
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_page_caching_demo
);
//Khởi động đồng thời yêu cầu dữ liệu cục bộ và yêu cầu HTTP từ xa
final
String
userId
=
"xxx"
;
localDataCache
.
getCachingData
(
userId
,
new
AsyncCallback
<
HttpResponse
>()
{
@Override
public
void
onResult
(
HttpResponse
data
)
{
if
(
data
!=
null
&&
!
dataFromHttpReady
)
{
//Có dữ liệu cũ trong bộ nhớ đệm & yêu cầu HTTP từ xa chưa trả vềVSBET, hiển thị dữ liệu cũ trước
processData
(
data
);
}
}
});
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest
(),
new
HttpListener
<
HttpRequest
,
HttpResponse
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest
request
,
HttpResult
<
HttpResponse
>
result
,
Object
contextData
)
{
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
dataFromHttpReady
=
true
;
processData
(
result
.
getResponse
());
//Từ HTTP kéo dữ liệu mới nhấtkết quả bóng đá việt nam hôm nay, cập nhật bộ nhớ đệm cục bộ
localDataCache
.
putCachingData
(
userId
,
result
.
getResponse
(),
null
);
}
else
{
processError
(
result
.
getErrorCode
());
}
}
},
null
);
}
private
void
processData
(
HttpResponse
data
)
{
//TODO: Cập nhật giao diện người dùngkết quả bóng đá việt nam hôm nay, hiển thị dữ liệu. Code ở đây bị lược bỏ
}
private
void
processError
(
int
errorCode
)
{
//TODO: Cập nhật giao diện người dùngkết quả bóng đá việt nam hôm nay, hiển thị lỗi. Code ở đây bị lược bỏ
}
}
Dù việc đọc dữ liệu từ bộ nhớ đệm cục bộ thường nhanh hơn nhiều so với việc lấy dữ liệu từ mạngVSBET, nhưng vì cả hai đều là giao diện bất đồng bộ (asynchronous), hoàn toàn có khả năng logic rằng dữ liệu từ mạng sẽ được trả về trước khi dữ liệu từ bộ nhớ đệm kích hoạt sự kiệ Hơn nữa, trong bài viết trước của chúng ta có đề cập đến chủ đề “... Xin lỗi, tôi đã quên mất tiêu đề chính xác của bài viết đó. Có lẽ bạn có thể giúp nhắc lại tên bài viết không? Điều này sẽ giúp tôi có thêm thông tin để mở rộng ý tưởng trong đoạn tiếp theo một cách phù hợp hơn. [Thêm một chút suy ngẫm] Có lẽ bài viết trước đã đề cập đến cách tối ưu hóa hiệu suất ứng dụng bằng cách cân bằng giữa việc sử dụng bộ nhớ đệm và kết nối mạng. Điều quan trọng ở đây là phải hiểu rõ quy trình hoạt động của cả hai phương thức này để đảm bảo ứng dụng hoạt động trơn tru nhất có thể. Xử lý bất đồng bộ trong phát triển Android và iOS (phần hai) —— Gọi lại cho tác vụ bất đồng bộ Trong phần “thứ tự gọi lại” của tài liệu nàyVSBET, việc đề cập đến “callback kết quả thất bại sớm” và “callback kết quả thành công sớm” đã cung cấp một cơ sở thực tế hơn cho trường hợp này. Điều này không chỉ giúp giải thích rõ ràng hơn về cách hoạt động của quy trình mà còn nhấn mạnh tầm quan trọng của việc xử lý các kịch bản khác nhau trong thời gian thực, từ đó tối ưu hóa hiệu suất và đảm bảo hệ thống vận hành trơn tru ngay cả khi gặp phải các tình huống bất ngờ.
Trong mã nguồn ở trênVSBET, nếu việc lấy dữ liệu từ mạng trả về trước khi dữ liệu từ bộ nhớ cache hoàn tất callback, chúng ta sẽ đánh dấu một biến logic kiểu boolean có tên là Khi nhiệm vụ lấy dữ liệu từ bộ nhớ cache kết thúc, chúng ta sẽ kiểm tra biến này để quyết định bỏ qua dữ liệu từ bộ nhớ cache. Ngoài ra, để tối ưu hóa hiệu suất và tránh tình trạng xung đột dữ liệu, chúng tôi còn thêm một cơ chế kiểm tra thời gian. Dữ liệu từ mạng luôn được ưu tiên hơn nếu nó mới hơn dữ liệu từ bộ nhớ cache ít nhất 5 phút. Điều này giúp đảm bảo rằng ứng dụng luôn hoạt động với thông tin mới nhất mà không bị ảnh hưởng bởi các vấn đề bất thường trong quá trình lưu trữ bộ nhớ cache.
thực hiện đồng thờikeo banh, ưu tiên một bên
thực thi song songVSBET, ưu tiên một bên
đấu vật tay không
Yêu cầu mạng song song
thực hiện yêu cầu mạng song song
Chúng ta có thể xem hai yêu cầu mạng song song như là hai Observablekết quả bóng đá việt nam hôm nay, sau đó sử dụng phép toán zip để kết hợp kết quả của chúng. Điều này làm cho mọi thứ trở nên gọn gàng hơn rất nhiều. Tuy nhiên, trước tiên chúng ta cần giải quyết một vấn đề khác: đóng gói giao diện yêu cầu mạng bất đồng bộ được đại diện bởi HttpService thành mộ Để làm điều này, chúng ta sẽ sử dụng các công cụ có sẵn trong thư viện quan sát để chuyển đổi cách tiếp cận của HttpService từ việc dựa trên callback sang mô hình quan sát. Điều này không chỉ giúp chúng ta dễ dàng tích hợp với các dòng dữ liệu khác mà còn tạo ra một mã nguồn sạch và dễ bảo trì hơn. Chẳng hạn, chúng ta có thể tạo một lớp wrapper bao quanh HttpService, nơi mỗi phương thức sẽ trả về một Observable thay vì thực hiện các thao tác trực tiếp. Điều này không chỉ làm tăng tính linh hoạt mà còn giảm thiểu sự phức tạp khi quản lý các luồng dữ liệu phức tạp trong ứng dụng.
Thông thườngVSBET, việc gói một nhiệm vụ đồng bộ thành Observable khá đơn giản, nhưng khi nói đến việc gói một nhiệm vụ bất đồng bộ sẵn có thành Observable thì lại không còn dễ hiểu như vậy. Trong trường hợp này, chúng ta cần sử dụng AsyncOnSubscribe để xử lý vấn đề. AsyncOnSubscribe cho phép chúng ta kiểm soát các bước của nhiệm vụ bất đồng bộ, từ đó tạo ra luồng dữ liệu theo cách mà Observable yêu cầu. Điều này đặc biệt hữu ích khi bạn muốn tích hợp các hệ thống hoặc API bất đồng bộ vào chuỗi xử lý dữ liệu của mình một cách linh hoạt và hiệu quả. Với sự hỗ trợ của AsyncOnSubscribe, việc chuyển đổi giữa các mô hình lập trình đồng bộ và bất đồng bộ trở nên mượt mà hơn bao giờ hết.
public
class
MultiRequestsDemoActivity
extends
AppCompatActivity
{
private
HttpService
httpService
=
new
MockHttpService
();
private
TextView
apiResultDisplayTextView
;
@Override
protected
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_multi_requests_demo
);
apiResultDisplayTextView
=
(
TextView
)
findViewById
(
R
.
id
.
api_result_display
);
/** * Trước tiênkết quả bóng đá việt nam hôm nay, sử dụng cơ chế AsyncOnSubscribe để đóng gói hai lần yêu cầu thành hai Observable riêng biệt */
Observable
<
HttpResponse1
>
request1
=
Observable
.
create
(
new
AsyncOnSubscribe
<
Integer
,
HttpResponse1
>()
{
@Override
protected
Integer
generateState
()
{
return
0
;
}
@Override
protected
Integer
next
(
Integer
state
,
long
requested
,
Observer
<
Observable
<?
extends
HttpResponse1
>>
observer
)
{
final
Observable
<
HttpResponse1
>
asyncObservable
=
Observable
.
create
(
new
Observable
.
OnSubscribe
<
HttpResponse1
>()
{
@Override
public
void
call
(
final
Subscriber
<?
super
HttpResponse1
>
subscriber
)
{
//Khởi động yêu cầu bất đồng bộ đầu tiên
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest1
(),
new
HttpListener
<
HttpRequest1
,
HttpResponse1
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest1
request
,
HttpResult
<
HttpResponse1
>
result
,
Object
contextData
)
{
//Yêu cầu bất đồng bộ đầu tiên kết thúcVSBET, gửi kết quả vào asyncObservable
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
subscriber
.
onNext
(
result
.
getResponse
());
subscriber
.
onCompleted
();
}
else
{
subscriber
.
onError
(
new
Exception
(
"request1 failed"
));
}
}
},
null
);
}
});
observer
.
onNext
(
asyncObservable
);
observer
.
onCompleted
();
return
1
;
}
});
Observable
<
HttpResponse2
>
request2
=
Observable
.
create
(
new
AsyncOnSubscribe
<
Integer
,
HttpResponse2
>()
{
@Override
protected
Integer
generateState
()
{
return
0
;
}
@Override
protected
Integer
next
(
Integer
state
,
long
requested
,
Observer
<
Observable
<?
extends
HttpResponse2
>>
observer
)
{
final
Observable
<
HttpResponse2
>
asyncObservable
=
Observable
.
create
(
new
Observable
.
OnSubscribe
<
HttpResponse2
>()
{
@Override
public
void
call
(
final
Subscriber
<?
super
HttpResponse2
>
subscriber
)
{
//Khởi động yêu cầu bất đồng bộ thứ hai
httpService
.
doRequest
(
"http://..."
,
new
HttpRequest2
(),
new
HttpListener
<
HttpRequest2
,
HttpResponse2
>()
{
@Override
public
void
onResult
(
String
apiUrl
,
HttpRequest2
request
,
HttpResult
<
HttpResponse2
>
result
,
Object
contextData
)
{
//Yêu cầu bất đồng bộ thứ hai kết thúckết quả bóng đá việt nam hôm nay, gửi kết quả vào asyncObservable
if
(
result
.
getErrorCode
()
==
HttpResult
.
SUCCESS
)
{
subscriber
.
onNext
(
result
.
getResponse
());
subscriber
.
onCompleted
();
}
else
{
subscriber
.
onError
(
new
Exception
(
"reques2 failed"
));
}
}
},
null
);
}
});
observer
.
onNext
(
asyncObservable
);
observer
.
onCompleted
();
return
1
;
}
});
//Hai Observable đại diện cho requestkeo banh, sử dụng zip để hợp nhất kết quả
Observable
.
zip
(
request1
,
request2
,
new
Func2
<
HttpResponse1
,
HttpResponse2
,
List
<
Object
>>()
{
@Override
public
List
<
Object
>
call
(
HttpResponse1
response1
,
HttpResponse2
response2
)
{
List
<
Object
>
responses
=
new
ArrayList
<
Object
>(
2
);
responses
.
add
(
response1
);
responses
.
add
(
response2
);
return
responses
;
}
}).
subscribe
(
new
Subscriber
<
List
<
Object
>>()
{
private
HttpResponse1
response1
;
private
HttpResponse2
response2
;
@Override
public
void
onNext
(
List
<
Object
>
responses
)
{
response1
=
(
HttpResponse1
)
responses
.
get
(
0
);
response2
=
(
HttpResponse2
)
responses
.
get
(
1
);
}
@Override
public
void
onCompleted
()
{
processData
(
response1
,
response2
);
}
@Override
public
void
onError
(
Throwable
e
)
{
processError
(
e
);
}
});
}
private
void
processData
(
HttpResponse1
data1
,
HttpResponse2
data2
)
{
//TODO: Cập nhật giao diện người dùngVSBET, hiển thị dữ liệu. Code ở đây bị lược bỏ
}
private
void
processError
(
Throwable
e
)
{
//TODO: Cập nhật giao diện người dùngkeo banh, hiển thị lỗi. Code ở đây bị lược bỏ
}
chuyển HttpService thành Observable
thực hiện song song nhưng ưu tiên một bên
thực thi tuần tự
Bài viết này lần lượt ba loại mối quan hệ hợp tác giữa các tác vụ bất đồng bộkeo banh, và cuối cùng không đi đến kết luận rằng cần chuyển đổi tất cả các tác vụ bất đồng bộ thành kiểu thực hiện tuần tự nối tiếp để làm đơn giản hóa logic xử lý. Việc lựa chọn vẫn nằm ở tay các nhà phát triển. Trong thực tế, mỗi loại hình tác vụ bất đồng bộ đều có những đặc điểm riêng biệt và cách tiếp cận phù hợp. Đôi khi việc chuyển đổi thành tuần tự hóa lại gây ra nhiều rủi ro hơn, chẳng hạn như gia tăng thời gian chờ đợi hoặc giảm hiệu suất tổng thể của ứng dụng. Vì vậy, thay vì áp dụng một quy tắc cứng nhắc, các nhà phát triển cần cân nhắc kỹ lưỡng từng trường hợp cụ thể dựa trên yêu cầu nghiệp vụ và cấu trúc hệ thống. Tóm lại, không có công thức nào áp dụng chung cho mọi tình huống. Điều quan trọng là phải hiểu rõ mục tiêu cuối cùng và ưu tiên hiệu quả trong việc tối ưu hóa mã nguồn. Chọn phương án nào phụ thuộc vào sự am hiểu sâu sắc và kinh nghiệm của người lập trình.
Hơn nữaVSBET, một vấn đề không thể bỏ qua là trong nhiều trường hợp, quyền lựa chọn không nằm trong tay chúng ta. Có thể cấu trúc mã nguồn mà chúng ta nhận được đã tạo ra những mối quan hệ hợp tác phức tạp giữa các tác vụ bất đồng bộ. Điều cần làm ở đây là khi tình huống như vậy xảy ra, chúng ta luôn phải giữ bình tĩnh, từ đó phân tích và xác định rõ ràng tình hình hiện tại thuộc loại nào trong số những tình huống rối rắm của mã nguồn logic. Đôi khi, việc hiểu rõ mọi thứ có vẻ như một nhiệm vụ khó khăn, nhưng nếu nhìn sâu vào từng chi tiết nhỏ nhất, bạn sẽ dần nhận ra rằng mỗi thành phần đều có ý nghĩa riêng của nó. Việc quan trọng hơn cả là phải biết cách kết nối tất cả các mảnh ghép lại với nhau để tạo nên bức tranh hoàn chỉnh. Điều này đòi hỏi sự kiên nhẫn và kỹ năng giải quyết vấn đề tốt. Hãy luôn nhớ rằng, ngay cả khi mọi thứ dường như đang trở nên hỗn loạn, chỉ cần bạn tập trung và phân tích cẩn thận, thì cuối cùng bạn cũng sẽ tìm ra hướng đi đúng đắn.
(Kết thúc)
Các bài viết được chọn lọc khác :